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

7.2 KiB

RF-CATALOG-001: Gestion de Contactos

Identificacion

Campo Valor
ID RF-CATALOG-001
Modulo MGN-005 Catalogs
Titulo Gestion de Contactos
Prioridad Alta
Estado Draft
Fecha 2025-12-05

Descripcion

El sistema debe permitir gestionar contactos (empresas, personas, direcciones) que representan clientes, proveedores, empleados y otros terceros relacionados con el tenant.

Referencia Odoo: res.partner (odoo/addons/base/models/res_partner.py)


Requisitos Funcionales

RF-CATALOG-001.1: Tipos de Contacto

El sistema debe soportar tres tipos de contacto:

Tipo Descripcion Puede tener hijos
company Empresa/Organizacion Si
individual Persona fisica independiente No
contact Contacto de una empresa (empleado) No

Reglas:

  • Un contacto tipo contact DEBE tener parent_id (empresa padre)
  • Un contacto tipo company puede tener contactos hijos
  • Un contacto tipo individual NO puede tener hijos

RF-CATALOG-001.2: Roles de Contacto

Un contacto puede tener uno o mas roles:

enum ContactRole {
  CUSTOMER = 'customer',   // Puede comprar
  VENDOR = 'vendor',       // Puede vender
  EMPLOYEE = 'employee',   // Empleado interno
  OTHER = 'other'          // Otro tipo
}

Reglas:

  • Un contacto puede ser cliente Y proveedor simultaneamente
  • El rol employee permite vincular con tabla users
  • Los roles determinan visibilidad en diferentes modulos

RF-CATALOG-001.3: Datos de Identificacion

Campos obligatorios y opcionales de identificacion:

Campo Obligatorio Descripcion
name Si Nombre completo
display_name Auto Computed: incluye parent name
ref No Codigo interno del tenant
vat Condicional RFC/NIT/RUT (obligatorio si es facturador)
company_registry No Registro mercantil

Validaciones:

  • VAT debe validarse segun formato del pais
  • REF debe ser unico dentro del tenant

RF-CATALOG-001.4: Datos de Contacto

Campo Tipo Validacion
email string Formato email valido
phone string Formato segun pais
mobile string Formato segun pais
website string URL valida

RF-CATALOG-001.5: Direcciones

El sistema debe soportar direccion estructurada:

Campo Tipo Descripcion
street string Calle principal
street2 string Linea adicional
city string Ciudad
zip string Codigo postal
state_id FK Estado/Provincia (global)
country_id FK Pais (global)

Referencia: El formato de direccion varia por pais (ver res.country.address_format)

RF-CATALOG-001.6: Contactos Hijos

Para empresas, se pueden agregar contactos relacionados:

interface ContactChild {
  parent_id: UUID;          // Empresa padre
  contact_type: 'contact';  // Siempre 'contact'
  function: string;         // Cargo/Puesto
  name: string;             // Nombre del contacto
  email: string;            // Email directo
  phone: string;            // Telefono directo
}

Reglas:

  • Un contacto hijo hereda direccion del padre si no tiene propia
  • Al eliminar empresa, contactos hijos se eliminan en cascada

RF-CATALOG-001.7: Vinculacion con Usuarios

Un contacto puede vincularse con un usuario del sistema:

interface ContactUserLink {
  contact_id: UUID;
  user_id: UUID;  // FK a core_users.users
}

Reglas:

  • Un usuario puede tener UN contacto asociado
  • Un contacto puede tener UN usuario asociado
  • Esta relacion se usa para empleados que son usuarios

Diferencia con Odoo: En Odoo, res.users hereda de res.partner mediante _inherits. Nosotros usamos FK explicita para mayor claridad.

RF-CATALOG-001.8: Campos Comerciales

Para clientes/proveedores:

Campo Tipo Descripcion
currency_id FK Moneda preferida
payment_term_days int Dias de credito
credit_limit decimal Limite de credito
bank_accounts JSONB Cuentas bancarias

RF-CATALOG-001.9: Tags/Categorias

Los contactos pueden tener multiples tags:

-- Tabla de tags
core_catalogs.contact_tags (
  id, tenant_id, name, color, parent_id
)

-- Relacion M:N
core_catalogs.contact_tag_rel (
  contact_id, tag_id
)

Operaciones CRUD

Crear Contacto

POST /api/v1/contacts
{
  "name": "Empresa Demo S.A.",
  "contactType": "company",
  "roles": ["customer", "vendor"],
  "vat": "RFC123456789",
  "email": "contacto@demo.com",
  "phone": "+52 55 1234 5678",
  "address": {
    "street": "Av. Principal 123",
    "city": "Ciudad de Mexico",
    "stateId": "uuid-estado",
    "countryId": "uuid-mexico",
    "zip": "06600"
  },
  "tags": ["uuid-tag-1", "uuid-tag-2"]
}

Listar Contactos

GET /api/v1/contacts?role=customer&type=company&search=demo&page=1&limit=20

Response:
{
  "data": [...],
  "meta": { "total": 100, "page": 1, "limit": 20 }
}

Busqueda Rapida

GET /api/v1/contacts/search?q=demo&limit=10

// Busca en: name, email, vat, ref, phone

Obtener Contacto con Hijos

GET /api/v1/contacts/:id?include=children,tags

Response:
{
  "id": "uuid",
  "name": "Empresa Demo",
  "children": [...],
  "tags": [...]
}

Reglas de Negocio

ID Regla Severidad
BR-001 VAT unico por tenant (si existe) Error
BR-002 REF unico por tenant (si existe) Error
BR-003 Email formato valido Error
BR-004 Contact type 'contact' requiere parent_id Error
BR-005 No eliminar contacto con transacciones Warning
BR-006 credit_limit >= 0 Error

Casos de Prueba

ID Escenario Resultado Esperado
TC-001 Crear empresa con datos completos Contacto creado exitosamente
TC-002 Crear contacto tipo 'contact' sin parent Error: parent_id requerido
TC-003 Crear contacto con VAT duplicado Error: VAT ya existe
TC-004 Buscar por nombre parcial Lista filtrada correctamente
TC-005 Agregar contacto hijo a empresa Relacion creada
TC-006 Eliminar empresa con hijos Cascada aplicada
TC-007 Vincular contacto con usuario Relacion 1:1 creada

Criterios de Aceptacion

  • CRUD completo de contactos
  • Soporte para 3 tipos de contacto
  • Relacion padre-hijo funcional
  • Tags/categorias asignables
  • Busqueda en menos de 100ms
  • Validacion de VAT por pais
  • Exportacion a CSV

Notas Tecnicas

Indice Recomendado

-- Busqueda rapida
CREATE INDEX idx_contacts_search ON contacts
USING gin(to_tsvector('spanish', name || ' ' || COALESCE(email, '') || ' ' || COALESCE(vat, '')));

-- Filtro por tipo y rol
CREATE INDEX idx_contacts_type_roles ON contacts(contact_type, roles) WHERE deleted_at IS NULL;

Computed Fields

-- display_name computed
CASE
  WHEN parent_id IS NOT NULL THEN
    (SELECT name FROM contacts WHERE id = parent_id) || ', ' || name
  ELSE name
END AS display_name

Historial

Version Fecha Autor Cambios
1.0 2025-12-05 System Creacion inicial