⚠ Alcance. Este inventario describe la rama de desarrollo
hquintero(commit35af6cdal 2026-08-19), que no está publicada. Nueve de las doce funcionalidades listadas no existen enmain/staging/develop. La columna «Estado» de cada tabla indica exactamente cuáles sí. Ver Arquitectura para lo que sí está desplegado.
Inventario del desarrollo custom sobre Medusa¶
Todo lo que se construyó encima de Medusa v2 para el e-commerce de Compulandia: qué se implementó, dónde vive cada pieza y cuál está realmente en producción.
| Archivos | 87 |
| Líneas | 12.339 |
| Módulos propios | 4 |
| Rutas API propias | 12 |
| Widgets de admin | 6 |
| Versión de Medusa | 2.15.5 |
| Período | septiembre 2025 – agosto 2026 |
| Commits / autores | 93 / 3 |
Panorama¶
Medusa v2 trae el núcleo de comercio —catálogo, carrito, órdenes, inventario, precios— pero no sabe nada de Paraguay. Todo el desarrollo custom llena exactamente ese hueco: cobrar por Bancard QR, calcular flete por caja y ciudad, notificar en castellano, y darle al equipo comercial pantallas de administración que Medusa no incluye.
Son 12.339 líneas repartidas en 87 archivos. Cerca de 900 son el script de seed; el resto es funcionalidad.
El trabajo se apoya en seis puntos de extensión de Medusa —módulos, rutas API, suscriptores, jobs, hooks de workflow y widgets de admin— más una capa propia de tiempo real que no corresponde a ninguno de ellos.
| Área | Archivos | Líneas |
|---|---|---|
src/admin |
44 | 4.866 |
src/modules |
13 | 3.157 |
src/api |
13 | 1.815 |
src/scripts |
3 | 961 |
src/subscribers |
9 | 799 |
src/lib |
2 | 360 |
src/workflows |
2 | 213 |
src/jobs |
1 | 168 |
Estado de despliegue¶
El dato más importante del inventario: la mayor parte de lo construido todavía no llegó a
producción. Las ramas main y staging llevan deliberadamente una porción chica del
desarrollo; el resto vive en la rama hquintero, pendiente de pruebas.
En producción — 3¶
Presente en main y staging.
| Funcionalidad | Tipo |
|---|---|
| Transferencia bancaria | payment |
| Editor de variantes en listas de precios | admin |
| Ajustes de UI del admin (2 widgets CSS) | admin |
En desarrollo — 9¶
Solo en hquintero, sin desplegar.
| Funcionalidad | Tipo |
|---|---|
| Pagos con Bancard QR | payment |
| Flete calculado MC Group | fulfillment |
| Notificaciones por email | notification |
| Login con Google | auth |
| Estado de pago en tiempo real | sse |
| Descuento por transferencia | promo |
| Gestión de geozonas | admin |
| Configuración de envíos | admin |
| Detalle de pago en la orden (intento de pago) | admin |
| Intentos de pago: página, detalle, widget en el pedido, rutas de admin y resumen diario (RF-010) | admin + API + job |
Módulos propios¶
Son la pieza más pesada del desarrollo: 3.157 líneas. Dos proveedores de pago, uno de envío y
uno de notificaciones, todos registrados en medusa-config.ts.
Bancard QR · en desarrollo¶
src/modules/bancard-qr/
Proveedor de pago con QR de Bancard, la red de pagos paraguaya.
Implementa el ciclo completo de un payment provider de Medusa —iniciar, autorizar, capturar, cancelar, reembolsar— más un cliente HTTP propio contra la API de Bancard y el manejo de webhooks de confirmación. El flujo real es asincrónico:
- El cliente elige Bancard QR y el backend genera el QR contra Bancard.
- La sesión de pago queda
pendingy se programa su expiración. - El cliente paga desde su app bancaria; Bancard llama al webhook.
- El backend marca el pago confirmado y avisa al storefront por SSE, sin que el cliente recargue.
Si nadie paga, el QR se cancela solo. La expiración usa un setTimeout por sesión para ser
precisa al segundo, y un job cada 5 minutos actúa de red de seguridad para los QR que quedaron
huérfanos por un reinicio del servidor.
4 archivos · 569 líneas · 3 rutas API + 1 webhook · 1 job programado
Ver también: Integración Bancard QR.
MC Group · en desarrollo¶
src/modules/mc-fulfillment/
Proveedor de envío con precio calculado por caja, ciudad y categoría.
El módulo más elaborado en lógica de negocio. En vez de una tarifa plana, calcula el flete en tiempo real cuando el cliente ingresa su dirección:
- Toma la ciudad de la dirección y busca su geozona.
- Verifica que MC Group opere en esa ciudad; si no, la opción no se ofrece.
- Separa los ítems en especiales (categorías con precio propio) y normales.
- Para los normales, busca el producto más grande y le asigna la caja más chica en la que entra.
- Suma el precio de esa caja más el de cada ítem especial por cantidad.
Los tipos de caja se configuran por depósito, y los precios por opción de envío, todo desde los widgets de admin. Las geozonas ya contemplan tres transportadoras —MC, CLX y AEX— con disponibilidad y plazo propios, así que sumar otro courier es configuración, no código.
3 archivos · 674 líneas · 11 tipos de dato · 2 widgets de configuración
Ver también: Fulfillment MC Group.
Notificaciones por email · en desarrollo¶
src/modules/email-notification/
Envío de correos transaccionales por SMTP con plantillas HTML propias.
15 plantillas escritas a mano, en castellano y con la identidad de Compulandia. Diez son para el cliente —bienvenida, recuperación de contraseña, verificación de correo, y el ciclo completo del pedido: confirmado, en preparación, enviado, entregado, cancelado, reembolsado, devolución— y cinco son avisos internos al equipo comercial (pedido nuevo, enviado, cancelado, devolución solicitada).
Es el módulo con más líneas del proyecto, y casi todas son plantilla: el contenido y los estilos viven separados de la lógica de envío, así que cambiar un texto no toca código.
4 archivos · 1.706 líneas · 15 plantillas · 9 suscriptores que lo usan
Transferencia bancaria · en producción¶
src/modules/bank-transfer/
Proveedor de pago sin cobro inmediato, con captura manual.
El más simple de los cuatro, y el único de esta lista que ya está cobrando. Permite cerrar la orden sin pago confirmado: queda pendiente, el vendedor verifica la transferencia y captura el pago desde el admin. Sin integración externa ni webhooks.
2 archivos · 208 líneas · 1 hook de promoción asociado
Rutas API propias¶
Endpoints que Medusa no expone y que el storefront o el admin necesitan. 1.815 líneas contando el middleware.
| Ruta | Método | Para qué | Estado |
|---|---|---|---|
/store/bancard-qr/generate |
POST | Genera el QR de pago y programa su expiración | desarrollo |
/store/bancard-qr/cancel |
POST | Cancela un QR vigente si el cliente cambia de método | desarrollo |
/webhooks/bancard-qr |
POST · GET | Recibe la confirmación de pago de Bancard | desarrollo |
/store/payment-status/:session_id |
GET | Consulta puntual del estado de una sesión de pago | desarrollo |
/payment-events/:session_id |
GET | Canal SSE: empuja el cambio de estado al storefront | desarrollo |
/store/geozones |
GET | Departamentos y ciudades de Paraguay con cobertura por courier | desarrollo |
/admin/fulfillment-sets/:id |
GET · POST | Lee y guarda tipos de caja (Medusa no expone este recurso) | desarrollo |
/admin/shipping-options/:id/metadata |
POST | Actualiza solo la metadata de precios sin tocar el resto | desarrollo |
/store/custom/products/:id |
GET | Producto con precios calculados por región resueltos | producción |
/admin/custom/price-lists/current |
GET | Listas de precios vigentes hoy en zona horaria de Asunción | producción |
/store/custom · /admin/custom |
GET | Endpoints de prueba del scaffold inicial | producción |
Suscriptores y jobs¶
Todo lo que reacciona sin que nadie lo pida. Los suscriptores escuchan eventos de Medusa y disparan correos; dos de ellos además avisan al equipo comercial.
| Evento | Qué dispara | Líneas |
|---|---|---|
order.placed |
Confirmación al cliente + aviso interno de pedido nuevo | 162 |
order.fulfillment_created |
Aviso de envío al cliente + aviso interno | 168 |
order.completed |
Correo de pedido entregado | 82 |
order.canceled |
Correo de cancelación | 72 |
order.return_requested |
Acuse de devolución solicitada | 98 |
order.return_received |
Confirmación de devolución recibida | 95 |
customer.created |
Correo de bienvenida | 59 |
auth.password_reset |
Correo de recuperación de contraseña | 49 |
product.created · updated · deleted |
Suscriptor de ejemplo del scaffold: solo escribe en consola | 14 |
El único job programado es bancard-qr-cleanup, que corre cada 5 minutos para revertir QR
huérfanos. No es el mecanismo principal de expiración —ese es el temporizador por sesión— sino
el que cubre los reinicios del servidor.
Descuento automático por transferencia¶
Una funcionalidad chica en líneas pero con lógica delicada, repartida entre dos hooks de workflow y un middleware.
La idea comercial es simple: pagar por transferencia bancaria tiene descuento. Lo difícil es que el descuento siga al método de pago mientras el cliente navega el checkout. La implementación cubre los tres momentos:
- Al crear la sesión de pago, un middleware aplica o quita la promoción según el método elegido, sin que el cliente escriba ningún código.
- Si alguien ingresa el código a mano, un hook valida que efectivamente tenga transferencia seleccionada.
- Si el cliente vuelve atrás en el checkout, otro hook remueve la promoción al refrescarse el carrito.
El código de promoción es configurable por variable de entorno (BANK_TRANSFER_PROMO_CODE); si
no está definida, toda esta lógica se desactiva sola.
Widgets de administración¶
4.866 líneas de React —el área con más archivos del proyecto— repartidas en 6 widgets y 36 archivos de componentes y hooks reutilizables.
| Widget | Dónde aparece | Qué permite hacer | Estado |
|---|---|---|---|
| Variantes de lista de precios | price_list.details |
Ver, buscar, agregar y quitar variantes con su precio normal, stock y precio especial | producción |
| Configuración de envíos | location.details |
Definir tipos de caja, precios por caja y categorías especiales por depósito | desarrollo |
| Geozonas | location.details |
Administrar ciudades por zona de servicio y cobertura de cada courier | desarrollo |
| Detalle de pago | order.details.side |
Ver datos del pagador, código de autorización y ticket de Bancard en la orden | desarrollo |
| Columna ancha en listas de precios | price_list.details |
Ensancha la columna de título para que entren nombres largos | producción |
| Descripción recortada | product.details |
Limita la descripción a tres líneas en la ficha de producto | producción |
Los dos últimos son inyecciones de CSS de treinta líneas, no aplicaciones. Los cuatro primeros sí son interfaces completas, con carga de datos, modales de edición y tablas propias.
Ver también: Widgets de administración de envíos.
Estado de pago en tiempo real¶
Una capa propia que no corresponde a ningún punto de extensión de Medusa: 360 líneas que resuelven cómo avisarle al storefront que el QR ya se pagó.
La decisión de diseño está documentada en el propio código: se eligió Server-Sent Events
sobre Socket.IO porque no requiere puerto adicional ni dependencias extra, y reutiliza el
servidor HTTP de Medusa. El storefront abre una conexión a /payment-events/:session_id y
espera; cuando llega el webhook de Bancard, el backend empuja el evento por ese canal.
La acompaña un gestor de expiraciones que programa un temporizador por cada QR emitido, en lugar
de sondear la base periódicamente. Es lo que permite que el QR expire al segundo exacto
configurado en BANCARD_QR_TIMEOUT_MS.
Infraestructura y operación¶
Fuera de src/ hay una capa de infraestructura completa, construida sobre todo entre junio y
agosto de 2026.
| Pieza | Qué resuelve |
|---|---|
Dockerfile |
Build multi-etapa: compila y deja una imagen final solo con dependencias de producción |
.github/workflows/build-deploy.yml |
Build a GHCR, deploy automático a staging, deploy a producción con aprobación manual |
deploy.sh |
Despliegue manual con backup de base, health check y rollback automático a la imagen anterior si falla |
docker-compose.yml |
Compose agnóstico al ambiente, con Postgres, Redis y el backend |
admin.nginx.conf |
Proxy del panel de administración |
docs/ |
Runbook, troubleshooting, CI/CD, ADR, integraciones y análisis |
La configuración de Medusa registra además dos cosas que no son código propio pero sí decisiones de arquitectura: Redis para caché, bus de eventos, motor de workflows y locking, y login con Google junto al de email y contraseña.
Ver también: Arquitectura y Runbook.
Pendientes conocidos¶
Cosas detectadas al levantar el proyecto que no bloquean el desarrollo pero sí conviene tener a la vista.
El seed corre en cada arranque de producción · riesgo¶
start.sh es el comando de arranque del contenedor y ejecuta migraciones, npm run seed y la
creación de un usuario administrador en cada inicio. El seed está protegido con || true, así
que un fallo no detiene el arranque —pero tampoco avisa. Además, la contraseña del administrador
está escrita en el archivo, que está versionado en el repositorio.
Ver ADR-0002 · Migraciones y bootstrap en start.sh.
El 61% del catálogo no puede completar el checkout · riesgo¶
1.578 de 2.574 productos publicados no tienen vinculado un shipping profile, y Medusa rechaza
cerrar cualquier carrito que los contenga con el error The cart items require shipping profiles
that are not satisfied by the current shipping methods.
La causa es que createProductsWorkflow solo crea ese vínculo si el payload trae
shipping_profile_id explícito, y el integrador que sincroniza el catálogo no lo envía. En
diciembre de 2025 se corrió un backfill puntual (936 vínculos), por eso los productos de ese mes
funcionan; todo lo importado antes y después está afectado.
El arreglo son dos piezas: un backfill de los productos existentes y un suscriptor de
product.created que asigne el perfil por defecto cuando falte.
Medusa 2.15.5 frente a 2.19.0 · atención¶
El proyecto está cuatro versiones menores atrás de la última estable. No es urgente —2.15.5 es de mayo de 2026— pero conviene encarar el upgrade después de estabilizar las features pendientes, no en paralelo.
Ver Upgrade Medusa 2.11.3 → 2.15.5 como referencia del esfuerzo del salto anterior.
Métodos de envío que se acumulan en el carrito · atención¶
Un carrito de prueba llegó a tener 19 métodos de envío asociados, varios duplicados. Sugiere que el storefront los agrega sin limpiar los anteriores. No rompe el checkout, pero ensucia el carrito.
Identidades de Google sin vincular · atención¶
Un cliente que se registró con email y contraseña y después intenta entrar con Google recibe un
422 Customer with this email already has an account. La identidad de Google queda creada sin
customer_id en su app_metadata, el token sale sin actor_id, y el storefront lo interpreta
como usuario nuevo. Medusa v2 no vincula identidades entre proveedores por email, por diseño.
Balance¶
Lo construido cubre las tres piezas que un e-commerce paraguayo necesita y Medusa no trae: cobrar por QR, calcular flete real por caja y ciudad y hablarle al cliente en castellano. Está escrito, compila contra 2.15.5 y arranca.
Lo que falta no es desarrollo, es validación: nueve de las doce funcionalidades nunca pasaron por staging. El próximo tramo de trabajo es probarlas y subirlas de a una, no seguir agregando.
Inventario levantado sobre la rama hquintero, commit 35af6cd, al 19/08/2026.