# RF-CATALOG-005: Categorias y Tags ## Identificacion | Campo | Valor | |-------|-------| | **ID** | RF-CATALOG-005 | | **Modulo** | MGN-005 Catalogs | | **Titulo** | Categorias y Tags | | **Prioridad** | Media | | **Estado** | Draft | | **Fecha** | 2025-12-05 | --- ## Descripcion El sistema debe proporcionar un sistema flexible de categorias y tags para organizar contactos, productos y otros registros. Las categorias son jerarquicas (arbol) mientras que los tags son planos con colores. **Referencia Odoo:** - `res.partner.category` (odoo/addons/base/models/res_partner.py) --- ## Requisitos Funcionales ### RF-CATALOG-005.1: Contact Tags Tags para clasificar contactos (clientes VIP, proveedores preferidos, etc.): | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | Identificador unico | | tenant_id | FK | Tenant propietario | | name | string | Nombre del tag | | color | int | Color (1-11) | | parent_id | FK | Tag padre (jerarquia) | | is_active | boolean | Disponible para uso | **Jerarquia:** - Tags pueden tener padre para organizacion - Display name incluye path: "Clientes / VIP / Gold" ### RF-CATALOG-005.2: Colores de Tags Sistema de colores predefinidos (similar a Odoo): | Color ID | Nombre | Hex | |----------|--------|-----| | 1 | Red | #F06050 | | 2 | Orange | #F4A460 | | 3 | Yellow | #F7CD1F | | 4 | Light Blue | #6CC1ED | | 5 | Dark Purple | #814968 | | 6 | Salmon Pink | #EB7E7F | | 7 | Medium Blue | #2C8397 | | 8 | Dark Blue | #475577 | | 9 | Fuchsia | #D6145F | | 10 | Green | #30C381 | | 11 | Purple | #9365B8 | ### RF-CATALOG-005.3: Asignacion de Tags a Contactos Relacion muchos a muchos: ```sql core_catalogs.contact_tag_rel ( contact_id UUID NOT NULL, tag_id UUID NOT NULL, PRIMARY KEY (contact_id, tag_id) ) ``` **Operaciones:** - Agregar tag a contacto - Remover tag de contacto - Obtener contactos por tag - Obtener tags de contacto ### RF-CATALOG-005.4: Categorias Genericas Sistema extensible de categorias para diferentes entidades: | Campo | Tipo | Descripcion | |-------|------|-------------| | id | UUID | Identificador unico | | tenant_id | FK | Tenant propietario | | entity_type | string | Tipo de entidad (contact, product, etc.) | | name | string | Nombre de categoria | | code | string | Codigo unico por tipo | | parent_id | FK | Categoria padre | | sort_order | int | Orden de visualizacion | | is_active | boolean | Disponible para uso | **Tipos de entidad soportados:** - `contact` - Categorias de contactos - `product` - Categorias de productos - `expense` - Categorias de gastos - `document` - Categorias de documentos ### RF-CATALOG-005.5: Jerarquia de Categorias Implementacion con path materializado para consultas eficientes: ```sql parent_path VARCHAR(255) -- Ej: "1/5/12/" ``` **Funciones:** - Obtener hijos directos - Obtener todos los descendientes - Obtener ancestros - Mover categoria (cambiar parent) ### RF-CATALOG-005.6: Display Name Computed El nombre mostrado incluye la jerarquia completa: ``` display_name = "Categoria Padre / Categoria Hijo / Esta Categoria" ``` --- ## Operaciones CRUD ### Crear Tag ```typescript POST /api/v1/catalogs/contact-tags { "name": "Cliente VIP", "color": 10, "parentId": null } ``` ### Listar Tags ```typescript GET /api/v1/catalogs/contact-tags Response: { "data": [ { "id": "uuid", "name": "Cliente VIP", "displayName": "Clientes / Cliente VIP", "color": 10, "contactsCount": 25 } ] } ``` ### Asignar Tags a Contacto ```typescript POST /api/v1/contacts/:id/tags { "tagIds": ["uuid-tag-1", "uuid-tag-2"] } ``` ### Buscar Contactos por Tag ```typescript GET /api/v1/contacts?tags=uuid-tag-1,uuid-tag-2 // Devuelve contactos que tienen TODOS los tags especificados ``` ### Crear Categoria ```typescript POST /api/v1/catalogs/categories { "entityType": "product", "name": "Electronicos", "code": "ELEC", "parentId": null } ``` ### Listar Categorias (Arbol) ```typescript GET /api/v1/catalogs/categories?entityType=product&format=tree Response: { "data": [ { "id": "uuid", "name": "Electronicos", "code": "ELEC", "children": [ { "id": "uuid", "name": "Computadoras", "code": "COMP", "children": [] }, { "id": "uuid", "name": "Celulares", "code": "CEL", "children": [] } ] } ] } ``` --- ## Data Seed ### Contact Tags por Defecto | Nombre | Color | Descripcion | |--------|-------|-------------| | Cliente VIP | 10 (green) | Clientes prioritarios | | Proveedor Preferido | 7 (blue) | Proveedores de confianza | | Prospecto | 3 (yellow) | Clientes potenciales | | Inactivo | 8 (dark blue) | Contactos sin actividad | ### Categorias de Producto por Defecto ``` Todos ├── Productos Vendibles │ ├── Bienes │ │ ├── Materias Primas │ │ └── Productos Terminados │ └── Servicios ├── Consumibles └── Activos Fijos ``` ### Categorias de Gasto por Defecto ``` Gastos ├── Operativos │ ├── Nomina │ ├── Servicios │ └── Mantenimiento ├── Administrativos │ ├── Papeleria │ └── Comunicacion └── Financieros └── Intereses ``` --- ## Reglas de Negocio | ID | Regla | Severidad | |----|-------|-----------| | BR-001 | Nombre unico por parent_id | Error | | BR-002 | Code unico por entity_type | Error | | BR-003 | No ciclos en jerarquia | Error | | BR-004 | Color entre 1-11 | Error | | BR-005 | No eliminar con registros asociados | Warning | --- ## Casos de Prueba | ID | Escenario | Resultado Esperado | |----|-----------|-------------------| | TC-001 | Crear tag con color | Tag creado | | TC-002 | Crear tag hijo | display_name incluye padre | | TC-003 | Asignar tag a contacto | Relacion creada | | TC-004 | Buscar por tag | Contactos filtrados | | TC-005 | Crear categoria jerarquica | parent_path calculado | | TC-006 | Mover categoria | parent_path actualizado | | TC-007 | Crear ciclo en jerarquia | Error | --- ## Criterios de Aceptacion - [ ] CRUD de contact tags - [ ] Sistema de colores funcional - [ ] Asignacion M:N tags-contactos - [ ] CRUD de categorias genericas - [ ] Jerarquia con parent_path - [ ] Display name computed - [ ] Seed de datos iniciales --- ## Notas Tecnicas ### Indice para Jerarquia ```sql -- Para consultas de descendientes CREATE INDEX idx_categories_parent_path ON core_catalogs.categories USING gin (parent_path gin_trgm_ops); -- Para validar ciclos CREATE OR REPLACE FUNCTION core_catalogs.check_category_cycle() RETURNS TRIGGER AS $$ BEGIN IF NEW.parent_id IS NOT NULL AND EXISTS ( SELECT 1 FROM core_catalogs.categories WHERE id = NEW.parent_id AND parent_path LIKE '%/' || NEW.id || '/%' ) THEN RAISE EXCEPTION 'Cycle detected in category hierarchy'; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; ``` ### Computed Display Name ```sql CREATE OR REPLACE FUNCTION core_catalogs.get_category_display_name(p_id UUID) RETURNS TEXT AS $$ WITH RECURSIVE ancestors AS ( SELECT id, name, parent_id, 1 as level FROM core_catalogs.categories WHERE id = p_id UNION ALL SELECT c.id, c.name, c.parent_id, a.level + 1 FROM core_catalogs.categories c JOIN ancestors a ON c.id = a.parent_id ) SELECT string_agg(name, ' / ' ORDER BY level DESC) FROM ancestors; $$ LANGUAGE SQL STABLE; ``` --- ## Historial | Version | Fecha | Autor | Cambios | |---------|-------|-------|---------| | 1.0 | 2025-12-05 | System | Creacion inicial |