--- id: "ET-SAAS-019" title: "Especificacion Tecnica Portfolio (Product Catalog)" type: "TechnicalSpec" status: "Implemented" priority: "P1" module: "portfolio" version: "1.0.0" created_date: "2026-02-03" updated_date: "2026-02-03" story_points: 8 --- # ET-SAAS-019: Especificacion Tecnica - Portfolio (Catalogo de Productos) ## Metadata - **Codigo:** ET-SAAS-019 - **Modulo:** Portfolio - **Version:** 1.0.0 - **Estado:** Implementado - **Fecha:** 2026-02-03 - **Basado en:** SAAS-019 --- ## 1. Resumen Ejecutivo ### 1.1 Estado Actual Sistema de catalogo de productos completamente implementado. | Capacidad | Estado | Notas | |-----------|--------|-------| | Categories | SI | Jerarquicas con arbol | | Products | SI | Multi-tipo, inventario | | Variants | SI | Atributos configurables | | Prices | SI | Multi-moneda, tiered | | RLS | SI | Row Level Security | ### 1.2 Funcionalidades v1.0 Catalogo completo con: - **4 Entidades**: Categories, Products, Variants, Prices - **Categorias jerarquicas**: Arbol ilimitado con parent_id - **5 Tipos de producto**: physical, digital, service, subscription, bundle - **Variantes**: Atributos dinamicos (color, size, etc.) - **Precios flexibles**: one_time, recurring, usage_based, tiered --- ## 2. Modelo de Datos ### 2.1 Schema: portfolio #### Tabla: categories | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | PK | | tenant_id | UUID | FK tenants | | parent_id | UUID | FK categories (jerarquia) | | name | VARCHAR(100) | Nombre categoria | | slug | VARCHAR(120) | URL-friendly (unique per tenant) | | description | TEXT | Descripcion | | position | INT | Orden dentro del padre | | image_url | VARCHAR(500) | Imagen | | color | VARCHAR(7) | Color hex | | icon | VARCHAR(50) | Icono | | is_active | BOOLEAN | Estado | | meta_title, meta_description | VARCHAR/TEXT | SEO | | custom_fields | JSONB | Campos personalizados | #### Tabla: products | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | PK | | tenant_id | UUID | FK tenants | | category_id | UUID | FK categories | | name | VARCHAR(255) | Nombre producto | | slug | VARCHAR(280) | URL-friendly | | sku | VARCHAR(100) | Stock Keeping Unit | | barcode | VARCHAR(100) | Codigo de barras | | description | TEXT | Descripcion larga | | short_description | VARCHAR(500) | Descripcion corta | | product_type | ENUM | physical, digital, service, subscription, bundle | | status | ENUM | draft, active, inactive, discontinued, out_of_stock | | base_price | DECIMAL(15,2) | Precio base | | cost_price | DECIMAL(15,2) | Costo | | compare_at_price | DECIMAL(15,2) | Precio tachado | | currency | VARCHAR(3) | Moneda (default USD) | | track_inventory | BOOLEAN | Trackear inventario | | stock_quantity | INT | Cantidad en stock | | low_stock_threshold | INT | Umbral de stock bajo | | allow_backorder | BOOLEAN | Permitir backorder | | weight, length, width, height | DECIMAL | Dimensiones | | images | JSONB | Array de URLs | | featured_image_url | VARCHAR(500) | Imagen principal | | tags | JSONB | Array de tags | | is_visible | BOOLEAN | Visible en frontend | | is_featured | BOOLEAN | Producto destacado | | has_variants | BOOLEAN | Tiene variantes | | variant_attributes | JSONB | Atributos para variantes | #### Tabla: variants | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | PK | | tenant_id | UUID | FK tenants | | product_id | UUID | FK products | | sku | VARCHAR(100) | SKU de variante | | barcode | VARCHAR(100) | Codigo de barras | | name | VARCHAR(255) | Nombre (e.g., "Rojo - XL") | | attributes | JSONB | {"color": "red", "size": "XL"} | | price | DECIMAL(15,2) | Override de precio | | cost_price | DECIMAL(15,2) | Costo variante | | stock_quantity | INT | Stock de variante | | weight | DECIMAL(10,3) | Peso (override) | | image_url | VARCHAR(500) | Imagen de variante | | is_active | BOOLEAN | Estado | | position | INT | Orden | #### Tabla: prices | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | PK | | tenant_id | UUID | FK tenants | | product_id | UUID | FK products (o variant_id) | | variant_id | UUID | FK variants (o product_id) | | price_type | ENUM | one_time, recurring, usage_based, tiered | | currency | VARCHAR(3) | Moneda | | amount | DECIMAL(15,2) | Precio | | compare_at_amount | DECIMAL(15,2) | Precio comparativo | | billing_period | VARCHAR(20) | day, week, month, year | | billing_interval | INT | Intervalo (e.g., 1 = mensual) | | min_quantity, max_quantity | INT | Rango para tiered | | valid_from, valid_until | TIMESTAMPTZ | Validez | | priority | INT | Prioridad (0 = default) | | is_active | BOOLEAN | Estado | ### 2.2 Enums ```sql portfolio.product_type: physical, digital, service, subscription, bundle portfolio.product_status: draft, active, inactive, discontinued, out_of_stock portfolio.price_type: one_time, recurring, usage_based, tiered portfolio.attribute_type: color, size, material, style, capacity, custom ``` --- ## 3. Arquitectura Backend ### 3.1 Estructura de Archivos ``` backend/src/modules/portfolio/ ├── portfolio.module.ts ├── controllers/ │ ├── categories.controller.ts │ └── products.controller.ts ├── services/ │ ├── categories.service.ts │ └── products.service.ts ├── entities/ │ ├── category.entity.ts │ ├── product.entity.ts │ ├── variant.entity.ts │ └── price.entity.ts ├── dto/ │ ├── create-category.dto.ts │ ├── create-product.dto.ts │ └── ... └── __tests__/ ``` ### 3.2 Endpoints API #### Categories | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /portfolio/categories | Listar (paginado) | | GET | /portfolio/categories/tree | Arbol jerarquico | | GET | /portfolio/categories/:id | Obtener | | POST | /portfolio/categories | Crear | | PUT | /portfolio/categories/:id | Actualizar | | DELETE | /portfolio/categories/:id | Eliminar | #### Products | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /portfolio/products | Listar (paginado, filtros) | | GET | /portfolio/products/:id | Obtener con variantes | | POST | /portfolio/products | Crear | | PUT | /portfolio/products/:id | Actualizar | | PATCH | /portfolio/products/:id/status | Cambiar status | | POST | /portfolio/products/:id/duplicate | Duplicar | | DELETE | /portfolio/products/:id | Eliminar | #### Variants | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /portfolio/products/:id/variants | Listar variantes | | POST | /portfolio/products/:id/variants | Crear variante | | PATCH | /portfolio/products/:id/variants/:vid | Actualizar | | DELETE | /portfolio/products/:id/variants/:vid | Eliminar | #### Prices | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /portfolio/products/:id/prices | Listar precios | | POST | /portfolio/products/:id/prices | Crear precio | | PATCH | /portfolio/products/:id/prices/:pid | Actualizar | | DELETE | /portfolio/products/:id/prices/:pid | Eliminar | --- ## 4. Seguridad ### 4.1 Row Level Security (RLS) ```sql -- Aislamiento por tenant CREATE POLICY categories_tenant_isolation ON portfolio.categories USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY products_tenant_isolation ON portfolio.products USING (tenant_id = current_setting('app.tenant_id')::uuid); ``` ### 4.2 Permisos RBAC | Permiso | Descripcion | |---------|-------------| | portfolio:read | Ver catalogo | | portfolio:write | Crear/editar productos | | portfolio:delete | Eliminar productos | | portfolio:publish | Publicar/despublicar | | portfolio:manage | Configuracion avanzada | --- ## 5. Frontend ### 5.1 Paginas Implementadas | Ruta | Componente | Estado | |------|------------|--------| | /dashboard/portfolio | PortfolioPage | Implementado | | /dashboard/portfolio/products | ProductsPage | Implementado | | /dashboard/portfolio/categories | CategoriesPage | Implementado | | /dashboard/portfolio/products/:id | ProductDetailPage | Pendiente | | /dashboard/portfolio/products/new | ProductFormPage | Pendiente | ### 5.2 Hooks ```typescript // frontend/src/hooks/usePortfolio.ts useCategories, useCategory, useCategoryTree useCreateCategory, useUpdateCategory, useDeleteCategory useProducts, useProduct useCreateProduct, useUpdateProduct, useDeleteProduct, useDuplicateProduct useProductVariants, useCreateVariant, useUpdateVariant, useDeleteVariant useProductPrices, useCreatePrice, useUpdatePrice, useDeletePrice ``` --- ## 6. Integraciones ### 6.1 Modulos Relacionados | Modulo | Integracion | |--------|-------------| | sales | Productos en oportunidades | | commissions | Comisiones por producto/categoria | | storage | Imagenes de productos | | audit | Log de cambios | ### 6.2 Eventos Emitidos ```typescript ProductCreated, ProductUpdated, ProductStatusChanged CategoryCreated, CategoryUpdated VariantCreated, PriceUpdated LowStockAlert // Cuando stock < threshold ``` --- ## 7. Filtros y Busqueda ### 7.1 Filtros Disponibles ```typescript interface ProductFilters { category_id?: string; product_type?: ProductType; status?: ProductStatus; is_visible?: boolean; is_featured?: boolean; min_price?: number; max_price?: number; search?: string; // Busca en name, sku, description tags?: string[]; sort_by?: 'name' | 'price' | 'created_at' | 'stock'; sort_order?: 'ASC' | 'DESC'; } ``` --- ## 8. Referencias - DDL: `database/ddl/schemas/portfolio/` - Backend: `backend/src/modules/portfolio/` - Frontend Services: `frontend/src/services/portfolio/` - Frontend Hooks: `frontend/src/hooks/usePortfolio.ts` - Frontend Pages: `frontend/src/pages/dashboard/portfolio/`