11 KiB
Sistema de Tracking Automático (mail.thread Pattern)
Fecha: 2025-11-24 Versión: 1.0 Estado: Implementado
Resumen Ejecutivo
El sistema de tracking automático implementa el patrón mail.thread de Odoo en PostgreSQL, proporcionando auditoría completa de cambios en registros críticos del sistema. Este sistema registra automáticamente:
- Creación de registros
- Cambios de estado (draft → confirmed → done)
- Modificaciones en campos críticos (monto, cliente, fechas)
- Eliminaciones (soft delete)
Arquitectura del Sistema
Componentes Principales
-
Tabla de Configuración (
system.field_tracking_config)- Define qué campos trackear por cada tabla
- Configurable sin modificar código
-
Tabla de Historial (
system.change_log)- Almacena todos los cambios registrados
- Optimizada para consultas rápidas
-
Función Trigger Genérica (
system.track_field_changes())- Se ejecuta automáticamente en INSERT/UPDATE/DELETE
- Lee configuración y registra cambios
-
Triggers por Tabla
- Activan el sistema en tablas críticas
- Sin impacto en performance (AFTER triggers)
Tablas del Sistema
1. system.field_tracking_config
Configuración de qué campos trackear:
CREATE TABLE system.field_tracking_config (
id UUID PRIMARY KEY,
table_schema VARCHAR(50) NOT NULL, -- 'financial', 'sales', etc.
table_name VARCHAR(100) NOT NULL, -- 'invoices', 'sales_orders', etc.
field_name VARCHAR(100) NOT NULL, -- 'status', 'amount_total', etc.
track_changes BOOLEAN DEFAULT true, -- Activar/desactivar tracking
field_type VARCHAR(50) NOT NULL, -- Tipo de dato
display_label VARCHAR(255) NOT NULL, -- Label para UI
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_field_tracking UNIQUE (table_schema, table_name, field_name)
);
Ejemplo de registros:
INSERT INTO system.field_tracking_config
(table_schema, table_name, field_name, field_type, display_label)
VALUES
('financial', 'invoices', 'status', 'text', 'Estado'),
('financial', 'invoices', 'amount_total', 'numeric', 'Monto Total'),
('sales', 'sales_orders', 'partner_id', 'uuid', 'Cliente');
2. system.change_log
Historial de cambios:
CREATE TABLE system.change_log (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL, -- Multi-tenancy
-- Referencia al registro modificado
table_schema VARCHAR(50) NOT NULL,
table_name VARCHAR(100) NOT NULL,
record_id UUID NOT NULL,
-- Usuario y fecha
changed_by UUID NOT NULL,
changed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Tipo de cambio
change_type VARCHAR(20) NOT NULL, -- 'create', 'update', 'delete', 'state_change'
-- Campo modificado
field_name VARCHAR(100), -- NULL para create/delete
field_label VARCHAR(255),
-- Valores
old_value TEXT,
new_value TEXT,
-- Metadata
change_context JSONB, -- Info adicional
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Índices optimizados:
CREATE INDEX idx_change_log_record
ON system.change_log(table_schema, table_name, record_id, changed_at DESC);
Función Trigger: system.track_field_changes()
Función genérica que se activa automáticamente:
CREATE OR REPLACE FUNCTION system.track_field_changes()
RETURNS TRIGGER AS $$
DECLARE
v_tenant_id UUID;
v_user_id UUID;
v_field_config RECORD;
BEGIN
-- 1. Registrar creación (INSERT)
IF TG_OP = 'INSERT' THEN
INSERT INTO system.change_log (...)
VALUES (..., 'create', ...);
RETURN NEW;
END IF;
-- 2. Registrar eliminación (soft delete)
IF TG_OP = 'UPDATE' AND NEW.deleted_at IS NOT NULL THEN
INSERT INTO system.change_log (...)
VALUES (..., 'delete', ...);
RETURN NEW;
END IF;
-- 3. Registrar cambios en campos configurados (UPDATE)
IF TG_OP = 'UPDATE' THEN
FOR v_field_config IN
SELECT field_name, display_label, field_type
FROM system.field_tracking_config
WHERE table_schema = TG_TABLE_SCHEMA
AND table_name = TG_TABLE_NAME
AND track_changes = true
LOOP
-- Comparar valores antiguo y nuevo
IF old_value IS DISTINCT FROM new_value THEN
INSERT INTO system.change_log (...);
END IF;
END LOOP;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Tablas con Tracking Activo
Financial Schema
| Tabla | Trigger | Campos Trackeados |
|---|---|---|
financial.invoices |
track_invoice_changes |
status, partner_id, invoice_date, amount_total, payment_term_id |
financial.journal_entries |
track_journal_entry_changes |
status, date, journal_id |
Purchase Schema
| Tabla | Trigger | Campos Trackeados |
|---|---|---|
purchase.purchase_orders |
track_purchase_order_changes |
status, partner_id, order_date, amount_total, receipt_status |
Sales Schema
| Tabla | Trigger | Campos Trackeados |
|---|---|---|
sales.sales_orders |
track_sales_order_changes |
status, partner_id, order_date, amount_total, invoice_status, delivery_status |
Inventory Schema
| Tabla | Trigger | Campos Trackeados |
|---|---|---|
inventory.stock_moves |
track_stock_move_changes |
status, product_id, product_qty, location_id, location_dest_id |
Projects Schema
| Tabla | Trigger | Campos Trackeados |
|---|---|---|
projects.projects |
track_project_changes |
status, name, manager_id, date_start, date_end |
Ejemplos de Uso
1. Ver historial completo de una factura
SELECT
cl.changed_at,
u.name AS changed_by_user,
cl.change_type,
cl.field_label,
cl.old_value,
cl.new_value
FROM system.change_log cl
JOIN auth.users u ON u.id = cl.changed_by
WHERE cl.table_schema = 'financial'
AND cl.table_name = 'invoices'
AND cl.record_id = '123e4567-e89b-12d3-a456-426614174000'
ORDER BY cl.changed_at DESC;
Resultado esperado:
changed_at | changed_by_user | change_type | field_label | old_value | new_value
--------------------|-----------------|--------------|----------------|-----------|----------
2025-11-24 10:30:00 | Juan Pérez | state_change | Estado | draft | open
2025-11-24 10:15:00 | Juan Pérez | update | Monto Total | 1000.00 | 1500.00
2025-11-24 10:00:00 | Juan Pérez | create | NULL | NULL | NULL
2. Ver quién cambió el estado de una orden
SELECT
changed_at,
old_value AS estado_anterior,
new_value AS estado_nuevo,
u.name AS usuario
FROM system.change_log cl
JOIN auth.users u ON u.id = cl.changed_by
WHERE cl.table_name = 'sales_orders'
AND cl.record_id = '<uuid-orden>'
AND cl.field_name = 'status'
ORDER BY cl.changed_at DESC;
3. Ver cambios en un rango de fechas
SELECT
cl.table_name,
cl.record_id,
cl.field_label,
cl.old_value,
cl.new_value,
u.name AS usuario
FROM system.change_log cl
JOIN auth.users u ON u.id = cl.changed_by
WHERE cl.table_schema = 'financial'
AND cl.changed_at BETWEEN '2025-11-01' AND '2025-11-30'
AND cl.change_type = 'state_change'
ORDER BY cl.changed_at DESC;
4. Auditoría: Cambios en montos de facturas
SELECT
cl.record_id,
i.number AS factura_numero,
cl.changed_at,
cl.old_value AS monto_anterior,
cl.new_value AS monto_nuevo,
u.name AS modificado_por
FROM system.change_log cl
JOIN financial.invoices i ON i.id = cl.record_id
JOIN auth.users u ON u.id = cl.changed_by
WHERE cl.table_name = 'invoices'
AND cl.field_name = 'amount_total'
AND cl.changed_at >= CURRENT_DATE - INTERVAL '7 days'
ORDER BY cl.changed_at DESC;
Agregar Tracking a Nuevas Tablas
Paso 1: Agregar configuración de campos
INSERT INTO system.field_tracking_config
(table_schema, table_name, field_name, field_type, display_label)
VALUES
('mi_schema', 'mi_tabla', 'status', 'text', 'Estado'),
('mi_schema', 'mi_tabla', 'amount', 'numeric', 'Monto'),
('mi_schema', 'mi_tabla', 'partner_id', 'uuid', 'Cliente');
Paso 2: Crear trigger en la tabla
CREATE TRIGGER track_mi_tabla_changes
AFTER INSERT OR UPDATE OR DELETE ON mi_schema.mi_tabla
FOR EACH ROW EXECUTE FUNCTION system.track_field_changes();
COMMENT ON TRIGGER track_mi_tabla_changes ON mi_schema.mi_tabla IS
'Registra automáticamente cambios en mi_tabla';
¡Listo! El tracking está activo automáticamente.
Desactivar Tracking de un Campo
UPDATE system.field_tracking_config
SET track_changes = false
WHERE table_schema = 'financial'
AND table_name = 'invoices'
AND field_name = 'notes';
Performance y Optimización
Índices Existentes
idx_change_log_record: Búsqueda por tabla/registroidx_change_log_changed_at: Búsqueda por fechaidx_change_log_record_date: Índice compuesto optimizado
Recomendaciones
- Archivado periódico: Mover registros antiguos a tabla de archivo
- Particionado: Considerar particionado por fecha si el volumen crece
- Monitoreo: Vigilar tamaño de
system.change_log
Query de Mantenimiento
-- Ver tamaño de la tabla de tracking
SELECT
pg_size_pretty(pg_total_relation_size('system.change_log')) AS total_size,
COUNT(*) AS total_records
FROM system.change_log;
-- Limpiar registros antiguos (ejecutar periódicamente)
DELETE FROM system.change_log
WHERE changed_at < CURRENT_DATE - INTERVAL '2 years'
AND change_type != 'state_change'; -- Mantener cambios de estado
Beneficios del Sistema
Para Auditoría
- Historial completo de cambios
- Respuesta inmediata a preguntas de "¿Quién modificó esto?"
- Cumplimiento de normativas (GDPR, SOX, etc.)
Para Debugging
- Rastrear errores de datos
- Detectar patrones de uso
- Investigar inconsistencias
Para Analytics
- Analizar flujos de trabajo
- Medir tiempos de procesamiento
- Identificar cuellos de botella
Troubleshooting
Problema: No se registran cambios
Causa: Trigger no creado o configuración faltante
Solución:
-- Verificar si existe el trigger
SELECT * FROM pg_trigger
WHERE tgname LIKE 'track_%';
-- Verificar configuración
SELECT * FROM system.field_tracking_config
WHERE table_name = 'mi_tabla';
Problema: Performance degradada
Causa: Volumen alto de cambios
Solución:
- Revisar índices
- Archivar datos antiguos
- Considerar particionado
Próximos Pasos
Mejoras Futuras (Opcional)
- API REST: Endpoint
/api/v1/change-log/:recordId - UI Component:
<ChangeHistory />en frontend - Notificaciones: Alertas en cambios críticos
- Rollback: Función para revertir cambios
Referencias
- Patrón mail.thread de Odoo: https://www.odoo.com/documentation/16.0/developer/reference/backend/orm.html#odoo.models.Model
- PostgreSQL Triggers: https://www.postgresql.org/docs/current/plpgsql-trigger.html
- Auditoría en PostgreSQL: https://www.postgresql.org/docs/current/ddl-rowsecurity.html
Documento creado por: Software Architect Agent Última actualización: 2025-11-24 Versión: 1.0