# Plan: Módulo Ecommerce

**Fecha**: Junio 2026  
**Fuente funcional**: Plan de ecommerce multi tenant sobre ERP Novasis + storefront `novasis-ecommerce-store`  
**Estado**: Diseño funcional/técnico inicial — documentación previa a implementación

---

## Decisiones de diseño

| # | Decisión | Resolución |
|---|---|---|
| 1 | Storefront oficial | Usar `novasis-ecommerce-store` como aplicación pública de ecommerce. |
| 2 | Backend | Usar el backend actual del ERP; no crear backend separado ni sincronizar schemas Prisma entre servicios. |
| 3 | Módulo backend | Implementar el módulo NestJS en `src/ecommerce`. |
| 4 | Persistencia | PostgreSQL + Prisma con diseño multiempresa; `empresa_id` obligatorio en tablas operativas. |
| 5 | Multi tenant MVP | Resolver tenant por subdominio. Dominios propios quedan para fase posterior. |
| 6 | Fuente de verdad | Reutilizar productos, imágenes, categorías, marcas, stock, listas de precios, ofertas, clientes, pagos y pedidos del ERP. |
| 7 | Checkout | Pago online obligatorio con Novasis Pay en el MVP. |
| 8 | Pedido ERP | Un checkout aprobado crea un `pedido` confirmado con `pedidos.origen_tipo = 'ecommerce'`. |
| 9 | Stock MVP | Validar disponibilidad antes del pago y reservar stock al confirmar el pedido ecommerce. |
| 10 | Personalización | Configuración visual parametrizada por empresa, no código por cliente. |
| 11 | Editor visual | Layout builder por bloques controlados; no editor libre tipo Figma. |
| 12 | Publicación | Separar borrador y versión publicada para permitir preview, publicación y rollback. |
| 13 | B2C/B2B | MVP mixto: invitado B2C y base preparada para login B2B con listas de precio por cliente. |
| 14 | Reutilización de pedidos | No crear tabla `ecommerce_order`; usar `pedidos`, `pedido_detalle`, `pedido_historial` y vínculo posterior con `factura_cab`. |
| 15 | Ecommerce standalone | Una empresa puede contratar `ECOMMERCE` como paquete principal, pero debe incluir Facturación. No debe requerir Contabilidad, Tesorería ni Cobranzas. |
| 16 | Reutilización de maestros | El menú Ecommerce puede abrir subpestañas/fachadas sobre Productos, Contactos/Clientes, Pedidos y Pagos existentes, ajustando permisos para empresas ecommerce-only. |
| 17 | Operación post-compra | Ecommerce debe tener una pantalla operativa para verificar datos, pago, comunicación con cliente, preparación y seguimiento del pedido. |
| 18 | Tracking | El seguimiento inicial se maneja con estados internos y mapa Leaflet opcional; integración AEX queda para fase posterior. |
| 19 | Perfiles operativos | El flujo post-compra debe separar funciones por perfiles: verificador, empaquetador/despachante, repartidor/seguimiento y supervisor ecommerce. |
| 20 | Workflow por empresa | Cada empresa configura su flujo operativo con presets y pasos opcionales; cada checkout congela un snapshot para que un cambio posterior no altere pedidos en curso. |
| 21 | Estados canónicos | La configuración puede omitir pasos y cambiar etiquetas, pero no inventar estados ni mezclar retiro con delivery. Retiro finaliza en `retirado`; delivery finaliza en `entregado`. |
| 22 | Concurrencia operativa | Tomar un pedido debe ser una transición atómica. Si dos verificadores actúan a la vez, sólo uno puede moverlo a `en_verificacion`. |
| 20 | Árbol de categorías | Reutilizar `categorias` como árbol multinivel (`padre_id`) para categoría, subcategoría y clasificación. No agregar `subcategoria_id` ni `clasificacion_id` a `productos`. |

---

## Flujo operativo vigente/propuesto

Este flujo describe el comportamiento esperado para la primera implementación funcional.

1. **Configuración desde ERP**
   - El usuario ingresa a `Configuración > Ecommerce`.
   - Define subdominio, estado, identidad visual, sucursal, depósito, lista de precios pública, reglas de checkout y bloques de layout.
   - Los cambios se guardan primero como borrador.

2. **Publicación de ecommerce**
   - El usuario revisa una vista previa responsive.
   - Al publicar, el sistema congela la configuración vigente en una versión publicada.
   - La versión anterior queda disponible para rollback.

3. **Navegación pública por subdominio**
   - `novasis-ecommerce-store` detecta el subdominio.
   - Consulta `GET /api/v1/ecommerce/public/config`.
   - Aplica CSS variables y layout desde la configuración publicada de la empresa.

4. **Catálogo**
   - El storefront consume catálogo público del ERP.
   - Solo muestra productos activos, no eliminados y pertenecientes al `empresa_id` resuelto.
   - Precios se calculan usando lista pública, lista B2B si corresponde y reglas vigentes del ERP.

5. **Carrito**
   - El carrito vive en el storefront y se valida contra backend al iniciar checkout.
   - El backend recalcula precios, ofertas, impuestos básicos y disponibilidad para evitar manipulación del cliente.

6. **Checkout y pago**
   - El backend crea una `ecommerce_checkout_session`.
   - Se crea una intención/sesión de pago en Novasis Pay.
   - El cliente paga antes de confirmar definitivamente el pedido.

7. **Creación de pedido**
   - El webhook aprobado de Novasis Pay marca la sesión como pagada.
   - El backend crea un `pedido` confirmado con `origen_tipo = 'ecommerce'`.
   - El webhook debe ser idempotente: no puede crear dos pedidos para el mismo pago.

8. **Operación posterior desde ERP**
   - El equipo operativo prepara el pedido.
   - Facturación es parte obligatoria del paquete Ecommerce.
   - El pedido ecommerce confirmado debe poder convertirse/facturarse usando el flujo existente que vincula `factura_cab.pedido_id` y marca `pedidos.estado = 'facturado'`.
   - Remisión, despacho, cobranzas, tesorería y contabilidad se integran sólo si esos módulos están activos.

9. **Seguimiento post-compra**
   - El operador revisa una bandeja de pedidos ecommerce.
   - Verifica datos del cliente, dirección, contacto, estado del pago y disponibilidad/reserva.
   - Registra comunicación con el cliente desde la ficha del pedido.
   - Actualiza estados operativos: recibido, en verificación, verificado, preparando, empaquetado, listo para retiro, listo para despacho, en reparto, entregado o con incidencia.
   - El cliente puede consultar el estado público con un token seguro.

10. **Separación por perfiles**
   - Verificador valida datos del cliente, pago, stock/reserva, dirección y observaciones.
   - Empaquetador prepara y confirma los productos físicos contra el pedido.
   - Despachante confirma salida, transportista, guía o entrega a retiro.
   - Repartidor/seguimiento actualiza ubicación/estado si la entrega es propia.
   - Supervisor ecommerce puede reasignar pedidos, resolver incidencias, revertir estados y auditar el flujo.

---

## Reutilización del ERP actual

### Pedidos

Ya existe una estructura de pedidos suficientemente completa para ecommerce:

| Tabla / modelo | Uso actual | Uso propuesto para Ecommerce |
|---|---|---|
| `pedidos` | Cabecera de pedido, estados, totales, cliente, sucursal, depósito, lista de precios, origen y vínculo a factura. | Cabecera del pedido ecommerce con `origen_tipo = 'ecommerce'` y `origen_id = ecommerce_checkout_session.id`. |
| `pedido_detalle` | Items con snapshot de descripción, unidad, cantidad, presentación, precio lista, precio negociado, descuentos, IVA y depósito. | Detalle del carrito aprobado, evitando duplicar lógica de productos/precios. |
| `pedido_historial` | Auditoría de acciones sobre el pedido. | Registrar creación desde ecommerce, pago aprobado, cambios operativos y facturación posterior. |
| `pedido_facturacion_parcial` | Vincula pedidos con facturas parciales. | Reutilizable cuando un pedido ecommerce se facture parcial o totalmente. |
| `factura_cab.pedido_id` / `pedidos.factura_id` | Vínculo pedido-factura. | Permite facturar pedidos ecommerce sin crear una integración paralela. |

Decisión: ecommerce no debe tener su propia tabla de orden/pedido comercial. `ecommerce_checkout_session` sólo representa el proceso público de checkout y pago; el documento operativo posterior es `pedidos`.

Consideración técnica vigente: el flujo actual de `PedidosService.confirmar()` valida y reserva stock al confirmar. Ecommerce debe alinearse con ese comportamiento para no vender stock que ya quedó comprometido por otro canal.

- El checkout aprobado debe crear un pedido y confirmarlo usando la política estándar con reserva.
- La reserva se registra sobre los depósitos resueltos por el pedido/detalle.
- Si la reserva falla por stock insuficiente entre el pago y la confirmación, la sesión queda en estado `error` y debe disparar gestión manual de devolución/reversión del pago.
- Una fase futura puede implementar reserva temporal antes del pago con expiración automática.

La regla es no duplicar estructura ni cálculos de pedido; ecommerce debe reutilizar el flujo de pedidos y reservar stock al confirmar.

### Facturación

El módulo de facturación ya soporta vínculo con pedido:

- `CreateFacturaDto` incluye `pedido_id`.
- `factura_cab` tiene `pedido_id`.
- `pedidos` tiene `factura_id`.
- Al crear una factura desde un pedido confirmado, el backend actual puede marcar el pedido como `facturado`.

Regla para ecommerce:

- Facturación es dependencia obligatoria del paquete Ecommerce.
- El pedido ecommerce confirmado debe facturarse desde el flujo estándar o desde una acción guiada dentro del menú Ecommerce.
- Ecommerce no debe depender de Contabilidad, Tesorería ni Cobranzas para facturar.
- Ecommerce no debe emitir factura automáticamente en MVP, salvo una fase posterior explícita de "auto facturación post pago".

### Productos, Contactos y precios

Ecommerce debe reutilizar los módulos existentes como fuente de verdad:

- `productos`, `producto_imagenes`, `producto_presentaciones`, `producto_atributos`, categorías y marcas para catálogo.
- `stock_deposito` para disponibilidad.
- `clientes` y `personas` para compradores B2B y clientes invitados normalizados.
- `lista_precios` y `lista_precios_productos` para precio público/B2B.
- `ofertas` y tablas relacionadas para promociones.

En el panel, el módulo Ecommerce debe ofrecer subpestañas que reutilicen estas capacidades sin obligar al usuario a navegar por todos los módulos ERP.

### Categorías multinivel para catálogo ecommerce

La administración de productos debe usar el árbol existente de `categorias` como jerarquía comercial multinivel:

| Nivel lógico | Representación técnica | Ejemplo |
|---|---|---|
| Categoría | `categorias` con `padre_id IS NULL` | Electrónica |
| Subcategoría | `categorias` con `padre_id` apuntando a categoría | Celulares |
| Clasificación | `categorias` con `padre_id` apuntando a subcategoría | Smartphones |

Reglas:

- `productos.categoria_id` debe apuntar al nodo más específico disponible, normalmente el tercer nivel lógico.
- No crear columnas `subcategoria_id` ni `clasificacion_id`; la ruta se obtiene recorriendo `categorias.padre_id`.
- El panel de categorías debe permitir crear tantos niveles como haga falta, pero el estándar funcional mínimo para ecommerce es 3 niveles.
- El panel de productos debe permitir seleccionar cualquier nodo del árbol, mostrando ruta completa, por ejemplo `Electrónica > Celulares > Smartphones`.
- El catálogo público debe poder filtrar por cualquier nivel. Si se filtra por una categoría padre, backend debe incluir productos asignados a todos sus descendientes.
- El storefront debe usar esta jerarquía para mega menú, carruseles por categoría, breadcrumbs, SEO de categoría y filtros.
- Para ecommerce, los labels visibles pueden configurarse como `Categoría`, `Subcategoría` y `Clasificación`, aunque técnicamente todos sean registros de `categorias`.

### Finanzas, pagos y contabilidad

El pago ecommerce debe iniciar en Novasis Pay. Los módulos financieros se integran de forma opcional:

- Sin Tesorería/Finanzas: guardar referencia del pago aprobado en `ecommerce_checkout_session` y en el pedido.
- Con Tesorería/Finanzas: permitir conciliación o movimiento financiero posterior.
- Con Contabilidad: generar asientos sólo cuando el flujo financiero/fiscal correspondiente exista y esté activo.

Ecommerce debe incluir Facturación para cerrar el ciclo comercial/fiscal. No debe depender de Contabilidad, Tesorería ni Cobranzas para vender y facturar.

---

## Menú Ecommerce en el panel ERP

El panel debe exponer un menú principal `Ecommerce` aunque la empresa no tenga otros módulos comerciales activos.

Subpestañas propuestas:

| Subpestaña | Fuente principal | Requiere módulo adicional |
|---|---|---|
| Dashboard | `ecommerce_evento_analitica`, pedidos ecommerce, checkout sessions | No |
| Configuración | `ecommerce_config`, `ecommerce_config_version` | No |
| Diseño y bloques | Config JSON versionada | No |
| Novedades / anuncios | `ecommerce_novedad` | No |
| Catálogo | `productos`, categorías, marcas, imágenes, stock | No, si está dentro de Ecommerce |
| Promociones | `ofertas` | No, si se habilita como subcapacidad ecommerce |
| Clientes | `clientes`, `personas` | No, si está dentro de Ecommerce |
| Pedidos | `pedidos` filtrado por `origen_tipo = 'ecommerce'` | No |
| Pagos | `ecommerce_checkout_session` + Novasis Pay | No |
| Verificación | `pedidos`, `clientes`, `ecommerce_checkout_session`, bitácora ecommerce | No |
| Comunicación | Bitácora ecommerce + email/WhatsApp/SMS según integraciones disponibles | No |
| Seguimiento | Estados ecommerce + ubicación/envío | No |
| Empaque / despacho | `ecommerce_pedido_tracking`, `pedido_detalle`, stock reservado | No |
| Facturación | `factura_cab` desde pedido | Incluida/requerida por Ecommerce |
| Finanzas / conciliación | Tesorería / Novasis Pay | Sí, sólo si Finanzas/Tesorería está activo |

Implicación técnica: las pantallas pueden reutilizar servicios/componentes existentes, pero los guards/permisos no deben exigir `INVENTARIO`, `VENTAS` o `CONTACTOS` para operar el ecommerce básico. Debe existir el módulo `ECOMMERCE` con subpermisos propios.

### Perfiles y permisos operativos

| Perfil | Responsabilidad | Permisos mínimos |
|---|---|---|
| Verificador ecommerce | Validar datos del cliente, pago, dirección, reserva y observaciones antes de preparar. | `ECOM_PED_VER`, `ECOM_PED_VERIFICAR`, `ECOM_PED_COMUNICAR` |
| Empaquetador ecommerce | Preparar productos, marcar faltantes, confirmar empaque y dejar listo para retiro/despacho. | `ECOM_PED_VER`, `ECOM_PED_EMPAQUETAR` |
| Despachante ecommerce | Confirmar salida, asignar transportista/repartidor, cargar guía y pasar a reparto/retiro. | `ECOM_PED_VER`, `ECOM_PED_DESPACHAR`, `ECOM_PED_TRACKING_EDITAR` |
| Repartidor ecommerce | Ver pedidos asignados, actualizar estado de entrega y ubicación manual. | `ECOM_PED_ASIGNADO_VER`, `ECOM_PED_ENTREGAR`, `ECOM_PED_TRACKING_EDITAR` |
| Atención ecommerce | Comunicarse con el cliente y registrar notas/incidencias sin modificar empaque/despacho. | `ECOM_PED_VER`, `ECOM_PED_COMUNICAR`, `ECOM_PED_INCIDENCIA` |
| Supervisor ecommerce | Reasignar, revertir estados controlados, resolver incidencias y auditar. | `ECOM_PED_SUPERVISAR`, `ECOM_PED_REASIGNAR`, `ECOM_PED_REVERTIR`, `ECOM_REPORTES_VER` |

### Flujo de estados operativos parametrizable

El motor usa estados canónicos y construye dos recorridos independientes. Los pasos de verificación, empaque y tránsito pueden activarse o desactivarse por empresa.

| Tramo | Retiro local | Delivery |
|---|---|---|
| Pago obligatorio | `pendiente_pago` → `pago_confirmado` | `pendiente_pago` → `pago_confirmado` |
| Control opcional | `en_verificacion` → `verificado` | `en_verificacion` → `verificado` |
| Preparación obligatoria | `preparando` | `preparando` |
| Empaque opcional | `empaquetado` | `empaquetado` |
| Puesta a disposición | `listo_retiro` | `listo_despacho` |
| Traslado opcional | No aplica | `en_camino` |
| Cierre | `retirado` | `entregado` |

Presets iniciales:

- `simple`: pago → preparación → listo → cierre.
- `controlado`: agrega verificación y empaque.
- `completo`: agrega verificación, empaque y `en_camino` para delivery.
- `personalizado`: combinación válida de los pasos opcionales anteriores.

Reglas no parametrizables:

- Un retiro local nunca puede pasar a `listo_despacho`, `en_camino` o `entregado`.
- Un delivery nunca puede pasar a `listo_retiro` o `retirado`.
- Ningún pedido puede entrar en preparación antes de `pago_confirmado`.
- Los estados finales no admiten avance ordinario.
- La cancelación usa endpoint y permiso específico porque libera stock y puede requerir reversión de pago/factura.
- Cada pedido conserva `workflow_snapshot`; los cambios de configuración sólo afectan checkouts nuevos.
- Cada transición se actualiza con condición sobre el estado anterior y registra `ecommerce_pedido_evento`. Esto evita la doble toma concurrente.

---

## Alcance funcional

| Módulo | Descripción | Prioridad |
|---|---|---|
| M01 | Configuración Ecommerce | Crítica |
| M02 | Theme y personalización | Crítica |
| M03 | Layout builder por bloques | Alta |
| M04 | Catálogo público | Crítica |
| M05 | Carrito y checkout | Crítica |
| M06 | Pagos Novasis Pay | Crítica |
| M07 | Pedidos ERP desde ecommerce | Crítica |
| M08 | B2B / login cliente | Alta |
| M09 | Analítica y métricas | Media |
| M10 | SEO / performance | Alta |
| M11 | Delivery / logística avanzada | Media |
| M12 | Menú Ecommerce en panel ERP | Crítica |
| M13 | Fachadas ecommerce sobre Productos/Contactos/Pedidos | Alta |
| M14 | Bandeja post-compra y verificación | Crítica |
| M15 | Comunicación con cliente | Alta |
| M16 | Seguimiento público del pedido | Alta |
| M17 | Tracking con mapa / AEX | Media |
| M18 | Perfiles operativos y bandejas por rol | Crítica |
| M19 | Novedades, anuncios y campañas laterales | Alta |

---

## Parámetros configurables

Estos parámetros viven por empresa y deben versionarse para preview, publicación y rollback.

### Identidad

- `nombre_comercial`
- `slogan`
- `logo_url`
- `favicon_url`
- `seo_title`
- `seo_description`

### Colores

- `primary`
- `secondary`
- `accent`
- `background`
- `surface`
- `text`
- `muted`
- `success`
- `danger`

### Tipografía

- `font_family`
- `font_scale`: `compact | normal | amplia`
- `heading_weight`
- `body_weight`

### Layout home

- `home_layout_mode`: `marketplace | hero_first | catalog_first`
- `show_category_sidebar`
- `show_mega_promos`
- `mega_promo_slots`: categorías, ofertas o campañas destacadas.
- `category_preview_columns`: columnas de navegación rápida por categoría.
- `hero_position`: `right | full_width | below_promos`
- `hero_variant`: `immersive | split`
- `motion_preset`: `none | subtle | dynamic`
- `transition_speed`: `fast | normal | relaxed`
- `section_background_mode`: `plain | alternating`
- `card_hover_effect`: `none | lift | zoom | glow`
- `header_variant`: `compact | standard`
- `sticky_header`, `show_scroll_animations`, `show_carousel_progress`: boolean

### Catálogo

- `card_size`: `compact | normal | large`
- `display_mode`: `category_carousels | all_with_pagination`
- `category_tree_enabled`: usar árbol multinivel de `categorias`
- `category_min_levels`: por defecto 3 para ecommerce
- `category_level_labels`: por defecto `["Categoría", "Subcategoría", "Clasificación"]`
- `category_filter_include_descendants`: por defecto `true`
- `category_selection_mode`: `leaf_preferred | any_node`
- `show_category_breadcrumb`: muestra ruta completa del producto
- `image_ratio`: `1:1 | 4:3 | 3:4`
- `columns_desktop`
- `columns_tablet`
- `columns_mobile`
- `max_products_per_category`: por defecto 10
- `page_size`: cantidad de productos por página en modo grilla
- `show_stock`
- `show_brand`
- `show_category`
- `show_promotion_badge`
- `show_installments`

### Buscador

- `search_position`: `header | hero | sidebar | sticky`
- `show_suggestions`
- `show_recent_searches`
- `show_popular_terms`

### Promociones

- `top_bar`
- `hero_banner`
- `promo_carousel`
- `catalog_inline_every_n_products`
- `product_card_badges`
- `checkout_upsell`

### Novedades y anuncios

- `news_enabled`
- `news_default_position`: `catalog_sidebar | home_section | catalog_inline`
- `news_items`: productos nuevos, anuncios, campañas, combos, temporadas y mensajes B2B.
- Los filtros del catálogo no deben ocupar el lateral por defecto; aparecen como `Refinar búsqueda` cuando existe una búsqueda global activa.
- Las novedades deben tener fecha desde/hasta, estado, prioridad, imagen, CTA y vínculo opcional a producto, categoría, marca, oferta o búsqueda predefinida.

### Temporadas

- `seasonal_templates`: lista versionada de temporadas por rango de fechas.
- Deben existir defaults para las cuatro estaciones: primavera, verano, otoño e invierno.
- Cada temporada puede quedar activa o inactiva por decisión del usuario.
- También pueden agregarse temporadas comerciales: vuelta a clases, navidad, liquidación, aniversario, etc.
- `seasonal_templates[].name`: primavera, verano, otoño, invierno, vuelta a clases, navidad, liquidación, etc.
- `seasonal_templates[].start_date`
- `seasonal_templates[].end_date`
- `seasonal_templates[].priority`
- `seasonal_templates[].theme_overrides`: colores, fondo, acento y superficie para esa temporada.
- `seasonal_templates[].promotion_overrides`: top bar, hero, imagen principal, badges y textos promocionales.
- `seasonal_templates[].decoration`: `none | snow | summer | spring | autumn | custom`
- `seasonal_templates[].enabled`: permite activar/desactivar la temporada sin eliminar su configuración.

### Checkout

- `layout`: `single_page | steps`
- `allow_guest_b2c`
- `require_account_before_checkout`: si está activo, exige iniciar sesión o crear cuenta antes de mostrar campos de checkout.
- `show_guest_checkout_option`: permite compra invitada sólo cuando `allow_guest_b2c` está activo.
- `payment_required`
- `payment_provider_label`: por defecto `Novasis Pay`
- `default_delivery_type`: `retiro | delivery`
- `require_terms_acceptance`
- `terms_url`
- `privacy_url`
- `invoice_fields`: documento fiscal, razón social y condición fiscal requerida.
- `guest_fields`: nombre, email, teléfono/WhatsApp y documento.

### Delivery

- `allow_pickup`
- `allow_delivery`
- `delivery_fixed_cost`
- `free_delivery_min_amount`
- `delivery_zones`

### Footer

- `footer_config.description`
- `footer_config.contact`: teléfono, WhatsApp, email, dirección y horario.
- `footer_config.link_groups`: grupos de enlaces parametrizables.
- `footer_config.payment_methods`: métodos de pago visibles.
- `footer_config.social_links`: redes sociales visibles.
- `footer_config.labels`: textos de confianza, contacto, pagos, redes, acciones y copyright.
- Grupos, enlaces y redes permiten activar/desactivar, editar y agregar elementos desde el panel ERP.
- `footer_config.show_powered_by_novasis`
- `content_pages_config`: páginas editables para contacto, términos, privacidad, FAQ, devoluciones, facturación y seguimiento.
- El desarrollador no debe intervenir para personalizar textos legales, soporte, contacto o preguntas frecuentes.

### Seguimiento

- `tracking_enabled`
- `tracking_public_token_enabled`
- `tracking_map_enabled`
- `tracking_provider`: `manual | leaflet | aex`
- `customer_notifications_enabled`
- `notification_channels`: `email | whatsapp | sms`
- `delivery_status_flow`
- `require_verification_before_packing`
- `require_packing_before_dispatch`
- `allow_supervisor_state_reversal`
- `default_dispatch_mode`: `retiro | delivery_propio | operador_logistico`

### Workflow operativo

- `workflow_config.preset`: `simple | controlado | completo | personalizado`.
- `workflow_config.verificationEnabled`: agrega `en_verificacion` y `verificado`.
- `workflow_config.packingEnabled`: agrega `empaquetado`.
- `workflow_config.transitEnabled`: agrega `en_camino` sólo para delivery.
- `workflow_config.cancellationPolicy`: `before_preparation | before_handoff | supervisor_only`.
- `workflow_config.labels`: etiquetas visibles opcionales por estado canónico.
- El backend normaliza y valida la configuración; el panel sólo ofrece combinaciones válidas.

---

## Modelo de datos propuesto

Nota: el ecommerce no duplica datos maestros. Productos, imágenes, categorías, marcas, stock, listas de precios, ofertas, clientes, pagos y pedidos se reutilizan desde el ERP.

### Reutilización de categorías existentes

No se propone tabla nueva para categorías ecommerce. Se reutiliza:

- `categorias.id`
- `categorias.empresa_id`
- `categorias.padre_id`
- `categorias.descripcion`
- `categorias.activo`
- `categorias.icono`
- `categorias.color`
- `productos.categoria_id`

Implicación técnica:

- El backend ecommerce debe exponer utilidades para obtener `category_path`, descendientes y árbol activo por empresa.
- Las consultas de catálogo por `categoria_id` deben expandir descendientes cuando `include_descendants=true`.
- El panel debe mejorar el selector de productos para mostrar el árbol completo, no sólo raíz e hijos directos.
- SEO y breadcrumbs de producto/categoría deben derivarse de la ruta del árbol.

### 1) Configuración principal

```sql
CREATE TYPE ecommerce_estado AS ENUM ('borrador', 'activa', 'pausada');

CREATE TABLE ecommerce_config (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id) ON DELETE CASCADE,
  subdominio VARCHAR(80) NOT NULL,
  estado ecommerce_estado NOT NULL DEFAULT 'borrador',
  nombre_comercial VARCHAR(160),
  slogan VARCHAR(255),
  logo_url VARCHAR(500),
  favicon_url VARCHAR(500),
  sucursal_id UUID REFERENCES empresas_sucursales(id),
  deposito_id UUID REFERENCES depositos(id),
  lista_precios_id UUID REFERENCES listas_precios(id),
  moneda VARCHAR(3) NOT NULL DEFAULT 'PYG',
  theme_config JSONB NOT NULL DEFAULT '{}'::jsonb,
  layout_config JSONB NOT NULL DEFAULT '{}'::jsonb,
  catalog_config JSONB NOT NULL DEFAULT '{}'::jsonb,
  promotion_slots JSONB NOT NULL DEFAULT '{}'::jsonb,
  seasonal_templates JSONB NOT NULL DEFAULT '[]'::jsonb,
  checkout_config JSONB NOT NULL DEFAULT '{}'::jsonb,
  workflow_config JSONB NOT NULL DEFAULT '{}'::jsonb,
  footer_config JSONB NOT NULL DEFAULT '{}'::jsonb,
  content_pages_config JSONB NOT NULL DEFAULT '[]'::jsonb,
  draft_config JSONB,
  published_version_id UUID,
  published_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),
  UNIQUE (empresa_id),
  UNIQUE (subdominio)
);
```

### 2) Versiones de configuración

```sql
CREATE TABLE ecommerce_config_version (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  ecommerce_config_id UUID NOT NULL REFERENCES ecommerce_config(id) ON DELETE CASCADE,
  empresa_id UUID NOT NULL REFERENCES empresas(id) ON DELETE CASCADE,
  version INT NOT NULL,
  status VARCHAR(20) NOT NULL DEFAULT 'published',
  config_snapshot JSONB NOT NULL,
  published_by UUID REFERENCES usuario(id),
  published_at TIMESTAMP DEFAULT NOW(),
  rollback_from_version_id UUID,
  UNIQUE (ecommerce_config_id, version)
);
```

### 3) Novedades y anuncios

```sql
CREATE TABLE ecommerce_novedad (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id) ON DELETE CASCADE,
  ecommerce_config_id UUID REFERENCES ecommerce_config(id) ON DELETE CASCADE,
  estado ecommerce_estado NOT NULL DEFAULT 'borrador',
  tipo VARCHAR(40) NOT NULL, -- producto_nuevo, anuncio, campana, combo, temporada, b2b
  titulo VARCHAR(160) NOT NULL,
  descripcion TEXT,
  tag VARCHAR(60),
  imagen_url VARCHAR(500),
  cta_label VARCHAR(80),
  target_tipo VARCHAR(40), -- producto, categoria, marca, oferta, busqueda, url
  target_id UUID,
  target_url VARCHAR(500),
  ubicacion VARCHAR(40) NOT NULL DEFAULT 'catalog_sidebar',
  segmento VARCHAR(40) NOT NULL DEFAULT 'todos', -- todos, invitado, b2c, b2b
  visible_desktop BOOLEAN NOT NULL DEFAULT true,
  visible_mobile BOOLEAN NOT NULL DEFAULT true,
  prioridad INT NOT NULL DEFAULT 0,
  fecha_desde TIMESTAMP,
  fecha_hasta TIMESTAMP,
  metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
  created_by UUID REFERENCES usuario(id),
  updated_by UUID REFERENCES usuario(id),
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);
```

### 4) Checkout público

```sql
CREATE TYPE ecommerce_checkout_estado AS ENUM (
  'pendiente_pago',
  'pago_confirmado',
  'en_verificacion',
  'verificado',
  'preparando',
  'empaquetado',
  'listo_retiro',
  'listo_despacho',
  'en_camino',
  'retirado',
  'entregado',
  'pedido_creado',
  'cancelado',
  'expirado',
  'error'
);

CREATE TABLE ecommerce_checkout_session (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id) ON DELETE CASCADE,
  ecommerce_config_id UUID NOT NULL REFERENCES ecommerce_config(id) ON DELETE CASCADE,
  cliente_id UUID REFERENCES clientes(id),
  token VARCHAR(80) NOT NULL UNIQUE,
  estado ecommerce_checkout_estado NOT NULL DEFAULT 'pendiente_pago',
  customer_snapshot JSONB NOT NULL,
  delivery_snapshot JSONB NOT NULL,
  items_snapshot JSONB NOT NULL,
  totals_snapshot JSONB NOT NULL,
  workflow_snapshot JSONB, -- obligatorio para checkouts nuevos; nullable sólo por compatibilidad histórica
  operador_asignado_id UUID REFERENCES usuario(id),
  asignado_at TIMESTAMP,
  payment_provider VARCHAR(40),
  payment_intent_id VARCHAR(120),
  payment_checkout_url VARCHAR(500),
  payment_raw_response JSONB,
  pedido_id UUID REFERENCES pedidos(id),
  error_message TEXT,
  expires_at TIMESTAMP NOT NULL,
  paid_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);
```

### 5) Sesión de cliente B2B

```sql
CREATE TABLE ecommerce_cliente_session (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id) ON DELETE CASCADE,
  cliente_id UUID NOT NULL REFERENCES clientes(id) ON DELETE CASCADE,
  token_hash VARCHAR(255) NOT NULL,
  email VARCHAR(120),
  expires_at TIMESTAMP NOT NULL,
  revoked_at TIMESTAMP,
  last_used_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW()
);
```

### 6) Analítica

```sql
CREATE TABLE ecommerce_evento_analitica (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id) ON DELETE CASCADE,
  ecommerce_config_id UUID REFERENCES ecommerce_config(id),
  session_id VARCHAR(120),
  cliente_id UUID REFERENCES clientes(id),
  evento VARCHAR(60) NOT NULL,
  entidad_tipo VARCHAR(40),
  entidad_id UUID,
  metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
  created_at TIMESTAMP DEFAULT NOW()
);
```

### 7) Seguimiento operativo

```sql
CREATE TABLE ecommerce_pedido_tracking (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id) ON DELETE CASCADE,
  pedido_id UUID NOT NULL REFERENCES pedidos(id) ON DELETE CASCADE,
  checkout_session_id UUID REFERENCES ecommerce_checkout_session(id),
  estado VARCHAR(40) NOT NULL DEFAULT 'recibido',
  estado_pago VARCHAR(40),
  estado_verificacion VARCHAR(40) NOT NULL DEFAULT 'pendiente',
  verificado_por UUID REFERENCES usuario(id),
  verificado_at TIMESTAMP,
  empaquetado_por UUID REFERENCES usuario(id),
  empaquetado_at TIMESTAMP,
  despachado_por UUID REFERENCES usuario(id),
  despachado_at TIMESTAMP,
  repartidor_id UUID REFERENCES usuario(id),
  canal_entrega VARCHAR(30),
  tracking_public_token VARCHAR(80) UNIQUE,
  tracking_provider VARCHAR(30) DEFAULT 'manual',
  tracking_external_id VARCHAR(120),
  latitud DECIMAL(10,7),
  longitud DECIMAL(10,7),
  direccion_entrega TEXT,
  fecha_estimada_entrega TIMESTAMP,
  delivered_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),
  UNIQUE (pedido_id)
);
```

### 8) Bitácora y comunicación

```sql
CREATE TABLE ecommerce_pedido_evento (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id UUID NOT NULL REFERENCES empresas(id) ON DELETE CASCADE,
  pedido_id UUID NOT NULL REFERENCES pedidos(id) ON DELETE CASCADE,
  tracking_id UUID REFERENCES ecommerce_pedido_tracking(id) ON DELETE CASCADE,
  tipo VARCHAR(40) NOT NULL, -- estado, pago, verificacion, comunicacion, nota, incidencia
  canal VARCHAR(30), -- interno, email, whatsapp, sms, telefono
  titulo VARCHAR(160),
  mensaje TEXT,
  metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
  visible_cliente BOOLEAN NOT NULL DEFAULT false,
  created_by UUID REFERENCES usuario(id),
  created_at TIMESTAMP DEFAULT NOW()
);
```

---

## APIs propuestas

### Admin ERP

| Método | Endpoint | Descripción |
|---|---|---|
| GET | `/api/v1/ecommerce/config` | Obtiene configuración actual y borrador de la empresa autenticada. |
| PUT | `/api/v1/ecommerce/config` | Guarda cambios de configuración como borrador. |
| POST | `/api/v1/ecommerce/config/publish` | Publica el borrador y crea una versión congelada. |
| POST | `/api/v1/ecommerce/config/rollback` | Restaura una versión publicada anterior. |
| GET | `/api/v1/ecommerce/catalogo/categorias/tree` | Devuelve árbol de categorías activo para configuración ecommerce. |
| GET | `/api/v1/ecommerce/catalogo/categorias/:id/path` | Devuelve ruta completa de una categoría. |
| GET | `/api/v1/ecommerce/novedades` | Lista novedades/anuncios configurados para la empresa. |
| POST | `/api/v1/ecommerce/novedades` | Crea una novedad o anuncio comercial. |
| PUT | `/api/v1/ecommerce/novedades/:id` | Actualiza contenido, vigencia, prioridad, segmento o vínculo. |
| DELETE | `/api/v1/ecommerce/novedades/:id` | Elimina o desactiva una novedad según política de auditoría. |

### Público

| Método | Endpoint | Descripción |
|---|---|---|
| GET | `/api/v1/ecommerce/public/config` | Resuelve tenant por subdominio y devuelve configuración publicada. |
| GET | `/api/v1/ecommerce/public/novedades` | Devuelve novedades activas por tenant, ubicación, vigencia y segmento. |
| GET | `/api/v1/ecommerce/public/categorias/tree` | Devuelve árbol público activo con niveles y rutas para mega menú/filtros. |
| GET | `/api/v1/ecommerce/public/catalogo` | Lista productos públicos con filtros, precio, stock y badges. Soporta `categoria_id` e `include_descendants=true`. |
| GET | `/api/v1/ecommerce/public/productos/:id` | Devuelve ficha pública con galería, descripción principal, `details_json`, especificaciones, reseñas, precio, stock comercial y relacionados. |
| POST | `/api/v1/ecommerce/public/checkout/session` | Valida carrito y crea sesión de checkout/pago. |
| POST | `/api/v1/ecommerce/public/webhook/novasis-pay` | Procesa pago aprobado/rechazado de Novasis Pay. |
| GET | `/api/v1/ecommerce/public/tracking/:token` | Devuelve estado público del pedido y tracking visible al cliente. |

### Operación post-compra

| Método | Endpoint | Descripción |
|---|---|---|
| GET | `/api/v1/ecommerce/pedidos` | Bandeja de pedidos ecommerce con filtros por estado, pago, verificación y entrega. |
| GET | `/api/v1/ecommerce/pedidos/:id` | Ficha operativa del pedido ecommerce. |
| PATCH | `/api/v1/ecommerce/pedidos/:id/verificacion` | Actualiza verificación de datos, pago o entrega. |
| PATCH | `/api/v1/ecommerce/pedidos/:id/empaque` | Marca preparación/empaque, faltantes o listo para despacho/retiro. |
| PATCH | `/api/v1/ecommerce/pedidos/:id/despacho` | Asigna repartidor/transportista, guía y salida del pedido. |
| PATCH | `/api/v1/ecommerce/pedidos/:id/tracking` | Actualiza estado/ubicación/tracking del pedido. |
| POST | `/api/v1/ecommerce/pedidos/:id/eventos` | Registra nota, incidencia o comunicación con el cliente. |
| POST | `/api/v1/ecommerce/pedidos/:id/notificar` | Envía comunicación al cliente por canal disponible. |
| POST | `/api/v1/ecommerce/pedidos/:id/reasignar` | Reasigna verificador, empaquetador, despachante o repartidor. |

---

## Fases de implementación

### Fase 0 — Documentación y modelo

- Crear este plan.
- Validar nomenclatura `ecommerce_*`.
- Definir migración inicial y contratos API antes de implementar.

### Fase 1 — MVP storefront + API pública

- Implementar `src/ecommerce` en backend.
- Crear tablas `ecommerce_config`, `ecommerce_config_version` y `ecommerce_checkout_session`.
- Reutilizar `categorias` como árbol multinivel para catálogo público.
- Implementar resolución de descendientes para filtro por categoría padre.
- Conectar `novasis-ecommerce-store` con config, catálogo, carrito y checkout.
- Crear pedido confirmado tras webhook aprobado usando `pedidos` y `pedido_detalle`.
- Guardar `origen_tipo = 'ecommerce'` y `origen_id = ecommerce_checkout_session.id`.
- Evitar duplicar lógica de pedido/facturación: ecommerce sólo orquesta checkout, pago y creación del pedido.

### Fase 2 — Panel ERP de personalización

- Agregar pantalla `Ecommerce` en el panel ERP.
- Configurar identidad, colores, tipografía, catálogo, buscador, promociones, checkout y delivery.
- Mejorar selector de categorías en productos para mostrar árbol completo y ruta `Categoría > Subcategoría > Clasificación`.
- Agregar validación visual para que productos ecommerce queden asignados al nivel más específico cuando corresponda.
- Implementar CRUD de novedades/anuncios para productos nuevos, campañas, combos, temporadas y mensajes B2B.
- Implementar preview responsive, publicación y rollback.
- Agregar subpestañas internas para catálogo, clientes, pedidos y pagos reutilizando datos existentes con permisos `ECOMMERCE`.
- Agregar bandeja post-compra con verificación de datos, pago, comunicación y seguimiento básico.
- Agregar bandejas por rol: verificación, empaque, despacho, reparto e incidencias.

### Fase 3 — Layout builder innovador

- Implementar builder por bloques: hero, buscador, categorías, destacados, promociones, más vendidos, marcas, B2B/mayorista.
- Agregar reglas de visibilidad desktop/mobile y segmentación básica.
- Preparar modo inteligente para sugerir ubicación de promociones según rendimiento.

### Fase 4 — B2B, métricas, SEO y escala

- Login cliente B2B.
- Historial y repetir pedido.
- Analítica de embudo.
- SEO por tenant/producto/categoría.
- Dominios propios.
- Reserva temporal de stock antes del pago, con expiración automática.
- Delivery avanzado.
- Tracking en tiempo real con Leaflet como primera versión.
- Integración AEX u operadores logísticos externos cuando el cliente lo contrate.

---

## Plan de pruebas

### Tenant y seguridad

- Subdominio activo resuelve la empresa correcta.
- Subdominio inexistente devuelve 404 público.
- Ecommerce pausado no permite checkout.
- Ningún endpoint público filtra datos de otra empresa.

### Configuración visual

- Cambios de colores, cards, buscador y promociones se reflejan en preview.
- Publicar actualiza storefront.
- Rollback restaura versión anterior.

### Catálogo

- Solo muestra productos activos/no eliminados.
- Respeta categoría, marca, búsqueda, lista de precios y stock.
- Categoría padre incluye productos de subcategorías/clasificaciones descendientes cuando `include_descendants=true`.
- Producto asignado a tercer nivel muestra breadcrumb completo: `Categoría > Subcategoría > Clasificación`.
- Selector del panel permite crear/usar al menos 3 niveles sin columnas adicionales en `productos`.
- Cards compactas, normales y grandes no rompen responsive.

### Checkout

- Backend recalcula precios y totales.
- Producto inactivo o sin stock suficiente bloquea checkout.
- Pago aprobado crea un solo pedido confirmado.
- Webhook duplicado no crea pedidos duplicados.
- Pedido ecommerce se puede facturar porque Facturación es dependencia obligatoria del paquete Ecommerce.
- Pedido ecommerce no requiere Contabilidad, Tesorería ni Cobranzas para completarse.

### Post-compra y seguimiento

- Pedido aprobado aparece en bandeja Ecommerce.
- Presets simple/controlado/completo generan los recorridos esperados.
- Cambiar el workflow no altera pedidos que ya tienen `workflow_snapshot`.
- Dos verificadores que intentan tomar el mismo pedido simultáneamente obtienen un solo ganador.
- Operador puede verificar datos del cliente, dirección y pago.
- Pedido verificado pasa a bandeja de empaque/despacho según tipo de entrega.
- Empaquetador puede marcar productos preparados, faltantes e incidencias.
- Despachante puede asignar repartidor/transportista y pasar a reparto o retiro.
- Operador puede registrar comunicación visible/interna.
- Estados de seguimiento se reflejan en vista pública por token.
- Leaflet muestra ubicación manual/última ubicación cuando tracking está habilitado.
- Incidencias quedan auditadas en `ecommerce_pedido_evento`.
- Usuario sin permiso de supervisor no puede saltar estados ni revertir transiciones.
- Retiro local nunca ofrece `en_camino` ni termina en `entregado`; finaliza en `retirado`.
- Delivery nunca ofrece `listo_retiro` ni `retirado`; finaliza en `entregado`.

### Dependencias y módulos opcionales

- `ECOMMERCE` debe incluir acceso mínimo a Facturación para emitir facturas desde pedidos ecommerce.
- Empresa con `ECOMMERCE` puede configurar catálogo, clientes, checkout, pagos, pedidos y facturación ecommerce.
- Empresa con `ECOMMERCE + TESORERIA/FINANZAS` puede conciliar pagos.
- Empresa con `ECOMMERCE + COBRANZAS` puede gestionar cuentas a cobrar/recibos si vende a crédito en fases futuras.
- Empresa con `ECOMMERCE + CONTABILIDAD` puede generar asientos desde facturas/movimientos, no desde ecommerce de forma aislada.

### UX

- Mobile, tablet y desktop sin solapamientos.
- Buscador funciona en header, hero, sidebar y sticky.
- Lighthouse básico aceptable para performance, SEO y accesibilidad.

---

## Supuestos

- Este documento es documentación inicial; no implementa tablas ni código.
- El módulo backend se llamará `ecommerce`.
- La app pública será `novasis-ecommerce-store`.
- El ERP seguirá siendo la fuente de verdad para productos, precios, stock, ofertas, clientes, pagos y pedidos.
- El MVP usa subdominio, pago obligatorio, reserva de stock al confirmar pedido y pedido confirmado tras pago aprobado.
