erp-core/docs/03-requerimientos/RF-catalogs/RF-CATALOG-005.md

7.5 KiB

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:

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:

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

POST /api/v1/catalogs/contact-tags
{
  "name": "Cliente VIP",
  "color": 10,
  "parentId": null
}

Listar Tags

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

POST /api/v1/contacts/:id/tags
{
  "tagIds": ["uuid-tag-1", "uuid-tag-2"]
}

Buscar Contactos por Tag

GET /api/v1/contacts?tags=uuid-tag-1,uuid-tag-2

// Devuelve contactos que tienen TODOS los tags especificados

Crear Categoria

POST /api/v1/catalogs/categories
{
  "entityType": "product",
  "name": "Electronicos",
  "code": "ELEC",
  "parentId": null
}

Listar Categorias (Arbol)

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

-- 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

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