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
contactDEBE tenerparent_id(empresa padre) - Un contacto tipo
companypuede tener contactos hijos - Un contacto tipo
individualNO 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
employeepermite vincular con tablausers - 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 |
|---|---|---|
| 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 |