--- id: "SAAS-019" title: "Portfolio" type: "Module" status: "Specified" priority: "P2" module: "portfolio" version: "1.0.0" created_date: "2026-01-24" updated_date: "2026-01-24" estimated_sp: 13 --- # SAAS-019: Portfolio ## Metadata - **Codigo:** SAAS-019 - **Modulo:** Portfolio - **Prioridad:** P2 - **Estado:** Especificado - **Fase:** 6 - Advanced Features - **Story Points:** 13 ## Descripcion Catalogo de productos y servicios para plataformas SaaS. Permite definir ofertas, precios, categorias y variantes. Se integra con Sales para cotizaciones y con Billing para suscripciones basadas en productos. ## Objetivos 1. Catalogo de productos/servicios configurable 2. Categorias y subcategorias jerarquicas 3. Precios con multiples monedas 4. Variantes de productos (tallas, colores, etc.) 5. Integracion con Sales y Billing ## Alcance ### Incluido - CRUD de productos/servicios - Categorias jerarquicas - Precios por moneda - Variantes basicas - Imagenes de productos - Productos activos/inactivos - Busqueda y filtros - Exportacion catalogo ### Excluido - Inventario (erp-core) - Ordenes de compra (erp-core) - Proveedores (erp-core) - Configurador de productos complejo ## Modelo de Datos ### Schema: portfolio ```sql -- Categorias CREATE TABLE portfolio.categories ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id), name VARCHAR(100) NOT NULL, slug VARCHAR(100) NOT NULL, description TEXT, parent_id UUID REFERENCES portfolio.categories(id), image_url VARCHAR(500), position INTEGER DEFAULT 0, is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT unique_category_slug UNIQUE (tenant_id, slug) ); -- Productos CREATE TABLE portfolio.products ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id), -- Info basica name VARCHAR(200) NOT NULL, slug VARCHAR(200) NOT NULL, description TEXT, short_description VARCHAR(500), -- Clasificacion type portfolio.product_type NOT NULL DEFAULT 'product', category_id UUID REFERENCES portfolio.categories(id), -- Codigo sku VARCHAR(100), barcode VARCHAR(100), -- Precios base_price DECIMAL(15,2) NOT NULL DEFAULT 0, currency VARCHAR(3) DEFAULT 'USD', tax_rate DECIMAL(5,2) DEFAULT 0, -- Estado status portfolio.product_status NOT NULL DEFAULT 'draft', is_featured BOOLEAN DEFAULT false, -- Media images JSONB DEFAULT '[]', thumbnail_url VARCHAR(500), -- Metadata tags JSONB DEFAULT '[]', attributes JSONB DEFAULT '{}', meta_title VARCHAR(200), meta_description VARCHAR(500), -- Audit created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT unique_product_slug UNIQUE (tenant_id, slug), CONSTRAINT unique_product_sku UNIQUE (tenant_id, sku) ); -- Variantes CREATE TABLE portfolio.variants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), product_id UUID NOT NULL REFERENCES portfolio.products(id) ON DELETE CASCADE, tenant_id UUID NOT NULL REFERENCES tenants.tenants(id), name VARCHAR(200) NOT NULL, sku VARCHAR(100), -- Precio diferencial price_adjustment DECIMAL(15,2) DEFAULT 0, price_type portfolio.price_type DEFAULT 'adjustment', -- 'adjustment' | 'fixed' -- Atributos (color, talla, etc.) attributes JSONB NOT NULL DEFAULT '{}', -- Estado is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW() ); -- Precios por moneda CREATE TABLE portfolio.prices ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), product_id UUID NOT NULL REFERENCES portfolio.products(id) ON DELETE CASCADE, variant_id UUID REFERENCES portfolio.variants(id) ON DELETE CASCADE, currency VARCHAR(3) NOT NULL, amount DECIMAL(15,2) NOT NULL, -- Precios especiales compare_at_price DECIMAL(15,2), -- Precio tachado cost_price DECIMAL(15,2), -- Costo -- Vigencia starts_at TIMESTAMPTZ, ends_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT unique_price UNIQUE (product_id, variant_id, currency) ); -- Enums CREATE TYPE portfolio.product_type AS ENUM ( 'product', 'service', 'digital', 'subscription' ); CREATE TYPE portfolio.product_status AS ENUM ( 'draft', 'active', 'inactive', 'archived' ); CREATE TYPE portfolio.price_type AS ENUM ( 'adjustment', 'fixed' ); ``` ### RLS Policies ```sql ALTER TABLE portfolio.categories ENABLE ROW LEVEL SECURITY; ALTER TABLE portfolio.products ENABLE ROW LEVEL SECURITY; ALTER TABLE portfolio.variants ENABLE ROW LEVEL SECURITY; ALTER TABLE portfolio.prices ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON portfolio.products USING (tenant_id = current_setting('app.current_tenant_id')::UUID); ``` ## Endpoints API ### Categories | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /portfolio/categories | Listar categorias | | POST | /portfolio/categories | Crear categoria | | GET | /portfolio/categories/:id | Obtener categoria | | PATCH | /portfolio/categories/:id | Actualizar categoria | | DELETE | /portfolio/categories/:id | Eliminar categoria | | GET | /portfolio/categories/tree | Arbol jerarquico | ### Products | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /portfolio/products | Listar productos | | POST | /portfolio/products | Crear producto | | GET | /portfolio/products/:id | Obtener producto | | PATCH | /portfolio/products/:id | Actualizar producto | | DELETE | /portfolio/products/:id | Eliminar producto | | POST | /portfolio/products/:id/duplicate | Duplicar producto | | PATCH | /portfolio/products/:id/status | Cambiar estado | ### Variants | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /portfolio/products/:id/variants | Listar variantes | | POST | /portfolio/products/:id/variants | Crear variante | | PATCH | /portfolio/variants/:id | Actualizar variante | | DELETE | /portfolio/variants/:id | Eliminar variante | ### Prices | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | /portfolio/products/:id/prices | Listar precios | | POST | /portfolio/products/:id/prices | Agregar precio | | PATCH | /portfolio/prices/:id | Actualizar precio | | DELETE | /portfolio/prices/:id | Eliminar precio | ## Frontend ### Paginas - `/portfolio` - Dashboard catalogo - `/portfolio/products` - Lista de productos - `/portfolio/products/new` - Crear producto - `/portfolio/products/:id` - Editar producto - `/portfolio/categories` - Gestion categorias ### Componentes - `ProductsList` - Tabla/grid de productos - `ProductForm` - Formulario producto completo - `ProductCard` - Tarjeta preview - `VariantsManager` - Gestion variantes - `PricesManager` - Gestion precios multi-moneda - `CategoryTree` - Arbol de categorias - `CategoryForm` - Formulario categoria - `ImageUploader` - Subida de imagenes - `ProductSearch` - Busqueda avanzada ### Hooks - `useProducts` - CRUD productos - `useCategories` - CRUD categorias - `useVariants` - CRUD variantes - `usePrices` - CRUD precios ## Dependencias ### Modulos Requeridos - SAAS-001 Auth - SAAS-002 Tenants - SAAS-011 Storage (imagenes) ### Modulos Opcionales - SAAS-018 Sales (productos en oportunidades) - SAAS-004 Billing (suscripciones) ## Criterios de Aceptacion 1. [ ] CRUD productos con validaciones 2. [ ] Categorias jerarquicas funcionales 3. [ ] Variantes con atributos dinamicos 4. [ ] Precios multi-moneda 5. [ ] Subida de imagenes integrada 6. [ ] Busqueda y filtros avanzados 7. [ ] Tests unitarios (>70% coverage) ## Estimacion | Componente | SP | |------------|-----| | DDL + Entities | 2 | | Backend Services | 3 | | Controllers + DTOs | 2 | | Frontend Pages | 3 | | Frontend Components | 2 | | Tests | 1 | | **Total** | **13** | --- **Ultima actualizacion:** 2026-01-24 **Autor:** Claude Opus 4.5