erp-core-database-v2/ddl/12-webhooks.sql
rckrdmrd 5043a640e4 refactor: Restructure DDL with numbered schema files
- Replace old DDL structure with new numbered files (01-24)
- Update migrations and seeds for new schema
- Clean up deprecated files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 00:40:32 -06:00

725 lines
24 KiB
PL/PgSQL

-- =============================================================
-- ARCHIVO: 12-webhooks.sql
-- DESCRIPCION: Sistema de webhooks, endpoints, entregas
-- VERSION: 1.0.0
-- PROYECTO: ERP-Core V2
-- FECHA: 2026-01-10
-- EPIC: SAAS-INTEGRATIONS (EPIC-SAAS-005)
-- HISTORIAS: US-060, US-061
-- =============================================================
-- =====================
-- SCHEMA: webhooks
-- =====================
CREATE SCHEMA IF NOT EXISTS webhooks;
-- =====================
-- TABLA: webhooks.event_types
-- Tipos de eventos disponibles para webhooks
-- =====================
CREATE TABLE IF NOT EXISTS webhooks.event_types (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Identificación
code VARCHAR(100) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL,
description TEXT,
category VARCHAR(50), -- sales, inventory, auth, billing, system
-- Schema del payload
payload_schema JSONB DEFAULT '{}',
-- Estado
is_active BOOLEAN DEFAULT TRUE,
is_internal BOOLEAN DEFAULT FALSE, -- Eventos internos no expuestos
-- Metadata
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- =====================
-- TABLA: webhooks.endpoints
-- Endpoints configurados por tenant
-- =====================
CREATE TABLE IF NOT EXISTS webhooks.endpoints (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
-- Identificación
name VARCHAR(200) NOT NULL,
description TEXT,
-- URL destino
url TEXT NOT NULL,
http_method VARCHAR(10) DEFAULT 'POST',
-- Autenticación
auth_type VARCHAR(30) DEFAULT 'none', -- none, basic, bearer, hmac, oauth2
auth_config JSONB DEFAULT '{}',
-- basic: {username, password}
-- bearer: {token}
-- hmac: {secret, header_name, algorithm}
-- oauth2: {client_id, client_secret, token_url}
-- Headers personalizados
custom_headers JSONB DEFAULT '{}',
-- Eventos suscritos
subscribed_events TEXT[] NOT NULL DEFAULT '{}',
-- Filtros
filters JSONB DEFAULT '{}',
-- Ejemplo: {"branch_id": ["uuid1", "uuid2"], "amount_gte": 1000}
-- Configuración de reintentos
retry_enabled BOOLEAN DEFAULT TRUE,
max_retries INTEGER DEFAULT 5,
retry_delay_seconds INTEGER DEFAULT 60,
retry_backoff_multiplier DECIMAL(3,1) DEFAULT 2.0,
-- Timeouts
timeout_seconds INTEGER DEFAULT 30,
-- Estado
is_active BOOLEAN DEFAULT TRUE,
is_verified BOOLEAN DEFAULT FALSE,
verified_at TIMESTAMPTZ,
-- Secreto para firma
signing_secret VARCHAR(255),
-- Estadísticas
total_deliveries INTEGER DEFAULT 0,
successful_deliveries INTEGER DEFAULT 0,
failed_deliveries INTEGER DEFAULT 0,
last_delivery_at TIMESTAMPTZ,
last_success_at TIMESTAMPTZ,
last_failure_at TIMESTAMPTZ,
-- Rate limiting
rate_limit_per_minute INTEGER DEFAULT 60,
rate_limit_per_hour INTEGER DEFAULT 1000,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
UNIQUE(tenant_id, url)
);
-- =====================
-- TABLA: webhooks.deliveries
-- Log de entregas de webhooks
-- =====================
CREATE TABLE IF NOT EXISTS webhooks.deliveries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
endpoint_id UUID NOT NULL REFERENCES webhooks.endpoints(id) ON DELETE CASCADE,
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
-- Evento
event_type VARCHAR(100) NOT NULL,
event_id UUID NOT NULL,
-- Payload enviado
payload JSONB NOT NULL,
payload_hash VARCHAR(64), -- SHA-256 para deduplicación
-- Request
request_url TEXT NOT NULL,
request_method VARCHAR(10) NOT NULL,
request_headers JSONB DEFAULT '{}',
-- Response
response_status INTEGER,
response_headers JSONB DEFAULT '{}',
response_body TEXT,
response_time_ms INTEGER,
-- Estado
status VARCHAR(20) NOT NULL DEFAULT 'pending',
-- pending, sending, delivered, failed, retrying, cancelled
-- Reintentos
attempt_number INTEGER DEFAULT 1,
max_attempts INTEGER DEFAULT 5,
next_retry_at TIMESTAMPTZ,
-- Error info
error_message TEXT,
error_code VARCHAR(50),
-- Timestamps
scheduled_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- =====================
-- TABLA: webhooks.events
-- Cola de eventos pendientes de envío
-- =====================
CREATE TABLE IF NOT EXISTS webhooks.events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
-- Tipo de evento
event_type VARCHAR(100) NOT NULL,
-- Payload del evento
payload JSONB NOT NULL,
-- Contexto
resource_type VARCHAR(100),
resource_id UUID,
triggered_by UUID REFERENCES auth.users(id),
-- Estado
status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending, processing, dispatched, failed
-- Procesamiento
processed_at TIMESTAMPTZ,
dispatched_endpoints INTEGER DEFAULT 0,
failed_endpoints INTEGER DEFAULT 0,
-- Deduplicación
idempotency_key VARCHAR(255),
-- Metadata
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMPTZ
);
-- =====================
-- TABLA: webhooks.subscriptions
-- Suscripciones individuales evento-endpoint
-- =====================
CREATE TABLE IF NOT EXISTS webhooks.subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
endpoint_id UUID NOT NULL REFERENCES webhooks.endpoints(id) ON DELETE CASCADE,
event_type_id UUID NOT NULL REFERENCES webhooks.event_types(id) ON DELETE CASCADE,
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
-- Filtros específicos para esta suscripción
filters JSONB DEFAULT '{}',
-- Transformación del payload
payload_template JSONB, -- Template para transformar el payload
-- Estado
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
UNIQUE(endpoint_id, event_type_id)
);
-- =====================
-- TABLA: webhooks.endpoint_logs
-- Logs de actividad de endpoints
-- =====================
CREATE TABLE IF NOT EXISTS webhooks.endpoint_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
endpoint_id UUID NOT NULL REFERENCES webhooks.endpoints(id) ON DELETE CASCADE,
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
-- Tipo de log
log_type VARCHAR(30) NOT NULL, -- config_changed, activated, deactivated, verified, error, rate_limited
-- Detalles
message TEXT,
details JSONB DEFAULT '{}',
-- Actor
actor_id UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- =====================
-- INDICES
-- =====================
-- Indices para event_types
CREATE INDEX IF NOT EXISTS idx_event_types_code ON webhooks.event_types(code);
CREATE INDEX IF NOT EXISTS idx_event_types_category ON webhooks.event_types(category);
CREATE INDEX IF NOT EXISTS idx_event_types_active ON webhooks.event_types(is_active) WHERE is_active = TRUE;
-- Indices para endpoints
CREATE INDEX IF NOT EXISTS idx_endpoints_tenant ON webhooks.endpoints(tenant_id);
CREATE INDEX IF NOT EXISTS idx_endpoints_active ON webhooks.endpoints(is_active) WHERE is_active = TRUE;
CREATE INDEX IF NOT EXISTS idx_endpoints_events ON webhooks.endpoints USING GIN(subscribed_events);
-- Indices para deliveries
CREATE INDEX IF NOT EXISTS idx_deliveries_endpoint ON webhooks.deliveries(endpoint_id);
CREATE INDEX IF NOT EXISTS idx_deliveries_tenant ON webhooks.deliveries(tenant_id);
CREATE INDEX IF NOT EXISTS idx_deliveries_event ON webhooks.deliveries(event_type, event_id);
CREATE INDEX IF NOT EXISTS idx_deliveries_status ON webhooks.deliveries(status);
CREATE INDEX IF NOT EXISTS idx_deliveries_pending ON webhooks.deliveries(status, next_retry_at)
WHERE status IN ('pending', 'retrying');
CREATE INDEX IF NOT EXISTS idx_deliveries_created ON webhooks.deliveries(created_at DESC);
-- Indices para events
CREATE INDEX IF NOT EXISTS idx_events_tenant ON webhooks.events(tenant_id);
CREATE INDEX IF NOT EXISTS idx_events_type ON webhooks.events(event_type);
CREATE INDEX IF NOT EXISTS idx_events_status ON webhooks.events(status);
CREATE INDEX IF NOT EXISTS idx_events_pending ON webhooks.events(status, created_at)
WHERE status = 'pending';
CREATE INDEX IF NOT EXISTS idx_events_idempotency ON webhooks.events(idempotency_key)
WHERE idempotency_key IS NOT NULL;
-- Indices para subscriptions
CREATE INDEX IF NOT EXISTS idx_subs_endpoint ON webhooks.subscriptions(endpoint_id);
CREATE INDEX IF NOT EXISTS idx_subs_event_type ON webhooks.subscriptions(event_type_id);
CREATE INDEX IF NOT EXISTS idx_subs_tenant ON webhooks.subscriptions(tenant_id);
-- Indices para endpoint_logs
CREATE INDEX IF NOT EXISTS idx_endpoint_logs_endpoint ON webhooks.endpoint_logs(endpoint_id);
CREATE INDEX IF NOT EXISTS idx_endpoint_logs_created ON webhooks.endpoint_logs(created_at DESC);
-- =====================
-- RLS POLICIES
-- =====================
-- Event types son globales (lectura pública)
ALTER TABLE webhooks.event_types ENABLE ROW LEVEL SECURITY;
CREATE POLICY public_read_event_types ON webhooks.event_types
FOR SELECT USING (is_active = TRUE AND is_internal = FALSE);
-- Endpoints por tenant
ALTER TABLE webhooks.endpoints ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_endpoints ON webhooks.endpoints
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
-- Deliveries por tenant
ALTER TABLE webhooks.deliveries ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_deliveries ON webhooks.deliveries
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
-- Events por tenant
ALTER TABLE webhooks.events ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_events ON webhooks.events
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
-- Subscriptions por tenant
ALTER TABLE webhooks.subscriptions ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_subscriptions ON webhooks.subscriptions
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
-- Endpoint logs por tenant
ALTER TABLE webhooks.endpoint_logs ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_endpoint_logs ON webhooks.endpoint_logs
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
-- =====================
-- FUNCIONES
-- =====================
-- Función para generar signing secret
CREATE OR REPLACE FUNCTION webhooks.generate_signing_secret()
RETURNS VARCHAR(255) AS $$
BEGIN
RETURN 'whsec_' || encode(gen_random_bytes(32), 'hex');
END;
$$ LANGUAGE plpgsql;
-- Función para crear un endpoint con secreto
CREATE OR REPLACE FUNCTION webhooks.create_endpoint(
p_tenant_id UUID,
p_name VARCHAR(200),
p_url TEXT,
p_subscribed_events TEXT[],
p_auth_type VARCHAR(30) DEFAULT 'none',
p_auth_config JSONB DEFAULT '{}',
p_created_by UUID DEFAULT NULL
)
RETURNS UUID AS $$
DECLARE
v_endpoint_id UUID;
BEGIN
INSERT INTO webhooks.endpoints (
tenant_id, name, url, subscribed_events,
auth_type, auth_config, signing_secret, created_by
) VALUES (
p_tenant_id, p_name, p_url, p_subscribed_events,
p_auth_type, p_auth_config, webhooks.generate_signing_secret(), p_created_by
) RETURNING id INTO v_endpoint_id;
-- Log de creación
INSERT INTO webhooks.endpoint_logs (endpoint_id, tenant_id, log_type, message, actor_id)
VALUES (v_endpoint_id, p_tenant_id, 'created', 'Endpoint created', p_created_by);
RETURN v_endpoint_id;
END;
$$ LANGUAGE plpgsql;
-- Función para emitir un evento
CREATE OR REPLACE FUNCTION webhooks.emit_event(
p_tenant_id UUID,
p_event_type VARCHAR(100),
p_payload JSONB,
p_resource_type VARCHAR(100) DEFAULT NULL,
p_resource_id UUID DEFAULT NULL,
p_triggered_by UUID DEFAULT NULL,
p_idempotency_key VARCHAR(255) DEFAULT NULL
)
RETURNS UUID AS $$
DECLARE
v_event_id UUID;
BEGIN
-- Verificar deduplicación
IF p_idempotency_key IS NOT NULL THEN
SELECT id INTO v_event_id
FROM webhooks.events
WHERE tenant_id = p_tenant_id
AND idempotency_key = p_idempotency_key
AND created_at > CURRENT_TIMESTAMP - INTERVAL '24 hours';
IF FOUND THEN
RETURN v_event_id;
END IF;
END IF;
-- Crear evento
INSERT INTO webhooks.events (
tenant_id, event_type, payload,
resource_type, resource_id, triggered_by,
idempotency_key, expires_at
) VALUES (
p_tenant_id, p_event_type, p_payload,
p_resource_type, p_resource_id, p_triggered_by,
p_idempotency_key, CURRENT_TIMESTAMP + INTERVAL '7 days'
) RETURNING id INTO v_event_id;
RETURN v_event_id;
END;
$$ LANGUAGE plpgsql;
-- Función para obtener endpoints suscritos a un evento
CREATE OR REPLACE FUNCTION webhooks.get_subscribed_endpoints(
p_tenant_id UUID,
p_event_type VARCHAR(100)
)
RETURNS TABLE (
endpoint_id UUID,
url TEXT,
auth_type VARCHAR(30),
auth_config JSONB,
custom_headers JSONB,
signing_secret VARCHAR(255),
timeout_seconds INTEGER,
filters JSONB
) AS $$
BEGIN
RETURN QUERY
SELECT
e.id as endpoint_id,
e.url,
e.auth_type,
e.auth_config,
e.custom_headers,
e.signing_secret,
e.timeout_seconds,
e.filters
FROM webhooks.endpoints e
WHERE e.tenant_id = p_tenant_id
AND e.is_active = TRUE
AND p_event_type = ANY(e.subscribed_events);
END;
$$ LANGUAGE plpgsql STABLE;
-- Función para encolar entrega
CREATE OR REPLACE FUNCTION webhooks.queue_delivery(
p_endpoint_id UUID,
p_event_type VARCHAR(100),
p_event_id UUID,
p_payload JSONB
)
RETURNS UUID AS $$
DECLARE
v_endpoint RECORD;
v_delivery_id UUID;
BEGIN
-- Obtener endpoint
SELECT * INTO v_endpoint
FROM webhooks.endpoints
WHERE id = p_endpoint_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Endpoint not found: %', p_endpoint_id;
END IF;
-- Crear delivery
INSERT INTO webhooks.deliveries (
endpoint_id, tenant_id, event_type, event_id,
payload, payload_hash,
request_url, request_method, request_headers,
max_attempts, status, scheduled_at
) VALUES (
p_endpoint_id, v_endpoint.tenant_id, p_event_type, p_event_id,
p_payload, encode(sha256(p_payload::text::bytea), 'hex'),
v_endpoint.url, v_endpoint.http_method, v_endpoint.custom_headers,
v_endpoint.max_retries + 1, 'pending', CURRENT_TIMESTAMP
) RETURNING id INTO v_delivery_id;
RETURN v_delivery_id;
END;
$$ LANGUAGE plpgsql;
-- Función para marcar entrega como completada
CREATE OR REPLACE FUNCTION webhooks.mark_delivery_completed(
p_delivery_id UUID,
p_response_status INTEGER,
p_response_headers JSONB,
p_response_body TEXT,
p_response_time_ms INTEGER
)
RETURNS BOOLEAN AS $$
DECLARE
v_delivery RECORD;
v_is_success BOOLEAN;
BEGIN
SELECT * INTO v_delivery
FROM webhooks.deliveries
WHERE id = p_delivery_id;
IF NOT FOUND THEN
RETURN FALSE;
END IF;
v_is_success := p_response_status >= 200 AND p_response_status < 300;
UPDATE webhooks.deliveries
SET
status = CASE WHEN v_is_success THEN 'delivered' ELSE 'failed' END,
response_status = p_response_status,
response_headers = p_response_headers,
response_body = LEFT(p_response_body, 10000), -- Truncar respuesta larga
response_time_ms = p_response_time_ms,
completed_at = CURRENT_TIMESTAMP
WHERE id = p_delivery_id;
-- Actualizar estadísticas del endpoint
UPDATE webhooks.endpoints
SET
total_deliveries = total_deliveries + 1,
successful_deliveries = successful_deliveries + CASE WHEN v_is_success THEN 1 ELSE 0 END,
failed_deliveries = failed_deliveries + CASE WHEN v_is_success THEN 0 ELSE 1 END,
last_delivery_at = CURRENT_TIMESTAMP,
last_success_at = CASE WHEN v_is_success THEN CURRENT_TIMESTAMP ELSE last_success_at END,
last_failure_at = CASE WHEN v_is_success THEN last_failure_at ELSE CURRENT_TIMESTAMP END,
updated_at = CURRENT_TIMESTAMP
WHERE id = v_delivery.endpoint_id;
RETURN v_is_success;
END;
$$ LANGUAGE plpgsql;
-- Función para programar reintento
CREATE OR REPLACE FUNCTION webhooks.schedule_retry(
p_delivery_id UUID,
p_error_message TEXT DEFAULT NULL
)
RETURNS BOOLEAN AS $$
DECLARE
v_delivery RECORD;
v_endpoint RECORD;
v_delay_seconds INTEGER;
BEGIN
SELECT d.*, e.retry_delay_seconds, e.retry_backoff_multiplier
INTO v_delivery
FROM webhooks.deliveries d
JOIN webhooks.endpoints e ON e.id = d.endpoint_id
WHERE d.id = p_delivery_id;
IF NOT FOUND THEN
RETURN FALSE;
END IF;
-- Verificar si quedan reintentos
IF v_delivery.attempt_number >= v_delivery.max_attempts THEN
UPDATE webhooks.deliveries
SET status = 'failed', error_message = p_error_message, completed_at = CURRENT_TIMESTAMP
WHERE id = p_delivery_id;
RETURN FALSE;
END IF;
-- Calcular delay con backoff exponencial
v_delay_seconds := v_delivery.retry_delay_seconds *
POWER(v_delivery.retry_backoff_multiplier, v_delivery.attempt_number - 1);
-- Programar reintento
UPDATE webhooks.deliveries
SET
status = 'retrying',
attempt_number = attempt_number + 1,
next_retry_at = CURRENT_TIMESTAMP + (v_delay_seconds || ' seconds')::INTERVAL,
error_message = p_error_message
WHERE id = p_delivery_id;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql;
-- Función para obtener estadísticas de un endpoint
CREATE OR REPLACE FUNCTION webhooks.get_endpoint_stats(
p_endpoint_id UUID,
p_days INTEGER DEFAULT 7
)
RETURNS TABLE (
total_deliveries BIGINT,
successful BIGINT,
failed BIGINT,
success_rate DECIMAL,
avg_response_time_ms DECIMAL,
deliveries_by_day JSONB,
errors_by_type JSONB
) AS $$
BEGIN
RETURN QUERY
SELECT
COUNT(*) as total_deliveries,
COUNT(*) FILTER (WHERE d.status = 'delivered') as successful,
COUNT(*) FILTER (WHERE d.status = 'failed') as failed,
ROUND(
COUNT(*) FILTER (WHERE d.status = 'delivered')::DECIMAL /
NULLIF(COUNT(*), 0) * 100, 2
) as success_rate,
ROUND(AVG(d.response_time_ms)::DECIMAL, 2) as avg_response_time_ms,
jsonb_object_agg(
COALESCE(DATE(d.created_at)::TEXT, 'unknown'),
day_count
) as deliveries_by_day,
jsonb_object_agg(
COALESCE(d.error_code, 'unknown'),
error_count
) FILTER (WHERE d.error_code IS NOT NULL) as errors_by_type
FROM webhooks.deliveries d
LEFT JOIN (
SELECT DATE(created_at) as day, COUNT(*) as day_count
FROM webhooks.deliveries
WHERE endpoint_id = p_endpoint_id
AND created_at > CURRENT_TIMESTAMP - (p_days || ' days')::INTERVAL
GROUP BY DATE(created_at)
) days ON TRUE
LEFT JOIN (
SELECT error_code, COUNT(*) as error_count
FROM webhooks.deliveries
WHERE endpoint_id = p_endpoint_id
AND error_code IS NOT NULL
AND created_at > CURRENT_TIMESTAMP - (p_days || ' days')::INTERVAL
GROUP BY error_code
) errors ON TRUE
WHERE d.endpoint_id = p_endpoint_id
AND d.created_at > CURRENT_TIMESTAMP - (p_days || ' days')::INTERVAL;
END;
$$ LANGUAGE plpgsql STABLE;
-- Función para limpiar entregas antiguas
CREATE OR REPLACE FUNCTION webhooks.cleanup_old_deliveries(p_days INTEGER DEFAULT 30)
RETURNS INTEGER AS $$
DECLARE
deleted_count INTEGER;
BEGIN
DELETE FROM webhooks.deliveries
WHERE created_at < CURRENT_TIMESTAMP - (p_days || ' days')::INTERVAL
AND status IN ('delivered', 'failed', 'cancelled');
GET DIAGNOSTICS deleted_count = ROW_COUNT;
-- También limpiar eventos procesados
DELETE FROM webhooks.events
WHERE created_at < CURRENT_TIMESTAMP - (p_days || ' days')::INTERVAL
AND status = 'dispatched';
RETURN deleted_count;
END;
$$ LANGUAGE plpgsql;
-- =====================
-- TRIGGERS
-- =====================
CREATE OR REPLACE FUNCTION webhooks.update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_event_types_updated_at
BEFORE UPDATE ON webhooks.event_types
FOR EACH ROW EXECUTE FUNCTION webhooks.update_timestamp();
CREATE TRIGGER trg_endpoints_updated_at
BEFORE UPDATE ON webhooks.endpoints
FOR EACH ROW EXECUTE FUNCTION webhooks.update_timestamp();
CREATE TRIGGER trg_subscriptions_updated_at
BEFORE UPDATE ON webhooks.subscriptions
FOR EACH ROW EXECUTE FUNCTION webhooks.update_timestamp();
-- =====================
-- SEED DATA: Event Types
-- =====================
INSERT INTO webhooks.event_types (code, name, category, description, payload_schema) VALUES
-- Sales events
('sale.created', 'Venta Creada', 'sales', 'Se creó una nueva venta', '{"type": "object", "properties": {"sale_id": {"type": "string"}, "total": {"type": "number"}}}'),
('sale.completed', 'Venta Completada', 'sales', 'Una venta fue completada y pagada', '{}'),
('sale.cancelled', 'Venta Cancelada', 'sales', 'Una venta fue cancelada', '{}'),
('sale.refunded', 'Venta Reembolsada', 'sales', 'Se procesó un reembolso', '{}'),
-- Inventory events
('inventory.low_stock', 'Stock Bajo', 'inventory', 'Un producto alcanzó el nivel mínimo de stock', '{}'),
('inventory.out_of_stock', 'Sin Stock', 'inventory', 'Un producto se quedó sin stock', '{}'),
('inventory.adjusted', 'Inventario Ajustado', 'inventory', 'Se realizó un ajuste de inventario', '{}'),
('inventory.received', 'Mercancía Recibida', 'inventory', 'Se recibió mercancía en el almacén', '{}'),
-- Customer events
('customer.created', 'Cliente Creado', 'customers', 'Se registró un nuevo cliente', '{}'),
('customer.updated', 'Cliente Actualizado', 'customers', 'Se actualizó información del cliente', '{}'),
-- Auth events
('user.created', 'Usuario Creado', 'auth', 'Se creó un nuevo usuario', '{}'),
('user.login', 'Inicio de Sesión', 'auth', 'Un usuario inició sesión', '{}'),
('user.password_reset', 'Contraseña Restablecida', 'auth', 'Un usuario restableció su contraseña', '{}'),
-- Billing events
('subscription.created', 'Suscripción Creada', 'billing', 'Se creó una nueva suscripción', '{}'),
('subscription.renewed', 'Suscripción Renovada', 'billing', 'Se renovó una suscripción', '{}'),
('subscription.cancelled', 'Suscripción Cancelada', 'billing', 'Se canceló una suscripción', '{}'),
('invoice.created', 'Factura Creada', 'billing', 'Se generó una nueva factura', '{}'),
('invoice.paid', 'Factura Pagada', 'billing', 'Se pagó una factura', '{}'),
('payment.received', 'Pago Recibido', 'billing', 'Se recibió un pago', '{}'),
('payment.failed', 'Pago Fallido', 'billing', 'Un pago falló', '{}'),
-- System events
('system.maintenance', 'Mantenimiento Programado', 'system', 'Se programó mantenimiento del sistema', '{}'),
('system.alert', 'Alerta del Sistema', 'system', 'Se generó una alerta del sistema', '{}')
ON CONFLICT (code) DO NOTHING;
-- =====================
-- COMENTARIOS
-- =====================
COMMENT ON TABLE webhooks.event_types IS 'Tipos de eventos disponibles para webhooks';
COMMENT ON TABLE webhooks.endpoints IS 'Endpoints configurados por tenant para recibir webhooks';
COMMENT ON TABLE webhooks.deliveries IS 'Log de entregas de webhooks con estado y reintentos';
COMMENT ON TABLE webhooks.events IS 'Cola de eventos pendientes de despacho';
COMMENT ON TABLE webhooks.subscriptions IS 'Suscripciones individuales evento-endpoint';
COMMENT ON TABLE webhooks.endpoint_logs IS 'Logs de actividad de endpoints';
COMMENT ON FUNCTION webhooks.emit_event IS 'Emite un evento a la cola de webhooks';
COMMENT ON FUNCTION webhooks.queue_delivery IS 'Encola una entrega de webhook';
COMMENT ON FUNCTION webhooks.mark_delivery_completed IS 'Marca una entrega como completada';
COMMENT ON FUNCTION webhooks.schedule_retry IS 'Programa un reintento de entrega';
COMMENT ON FUNCTION webhooks.get_endpoint_stats IS 'Obtiene estadísticas de un endpoint';