DDL-SPEC: Schema core_notifications
Identificacion
| Campo |
Valor |
| Schema |
core_notifications |
| Modulo |
MGN-008 |
| Version |
1.0 |
| Estado |
En Diseno |
| Autor |
Requirements-Analyst |
| Fecha |
2025-12-05 |
Descripcion General
El schema core_notifications gestiona notificaciones multi-canal (in-app, email, push), templates de mensajes y preferencias de usuario. Soporta notificaciones en tiempo real via WebSocket y cola asíncrona para email/push.
RF Cubiertos
| RF |
Titulo |
Tablas |
| RF-NOTIF-001 |
Notificaciones In-App |
notifications |
| RF-NOTIF-002 |
Email |
email_templates, email_jobs |
| RF-NOTIF-003 |
Push |
push_subscriptions, push_jobs |
| RF-NOTIF-004 |
Preferencias |
notification_preferences |
Diagrama ER
erDiagram
notifications {
uuid id PK
uuid tenant_id FK
uuid user_id FK
varchar type
varchar title
text body
jsonb data
varchar[] channels
boolean is_read
timestamptz read_at
timestamptz created_at
}
email_templates {
uuid id PK
uuid tenant_id
varchar key UK
varchar name
varchar subject
text body_html
text body_text
jsonb variables
boolean is_active
}
email_jobs {
uuid id PK
uuid tenant_id
uuid template_id FK
varchar to_email
varchar subject
text body_html
jsonb variables
varchar status
int attempts
timestamptz scheduled_at
timestamptz sent_at
}
push_subscriptions {
uuid id PK
uuid user_id FK
varchar endpoint UK
jsonb keys
varchar device_type
boolean is_active
timestamptz last_used_at
}
push_jobs {
uuid id PK
uuid subscription_id FK
varchar title
text body
jsonb data
varchar status
int attempts
timestamptz sent_at
}
notification_preferences {
uuid id PK
uuid user_id FK
varchar notification_type
boolean in_app
boolean email
boolean push
varchar email_frequency
}
email_templates ||--o{ email_jobs : "usa"
push_subscriptions ||--o{ push_jobs : "recibe"
Tablas
1. notifications
Notificaciones in-app para usuarios.
| Columna |
Tipo |
Nullable |
Default |
Descripcion |
id |
UUID |
NOT NULL |
gen_random_uuid() |
PK |
tenant_id |
UUID |
NOT NULL |
- |
FK a tenants |
user_id |
UUID |
NOT NULL |
- |
FK a users |
type |
VARCHAR(50) |
NOT NULL |
- |
Tipo de notificacion |
title |
VARCHAR(255) |
NOT NULL |
- |
Titulo |
body |
TEXT |
NOT NULL |
- |
Contenido |
data |
JSONB |
NULL |
'{}' |
Datos adicionales |
action_url |
VARCHAR(500) |
NULL |
- |
URL de accion |
action_label |
VARCHAR(100) |
NULL |
- |
Texto del boton |
icon |
VARCHAR(50) |
NULL |
- |
Icono |
channels |
VARCHAR(20)[] |
NOT NULL |
'{in_app}' |
Canales enviados |
is_read |
BOOLEAN |
NOT NULL |
false |
Leida |
read_at |
TIMESTAMPTZ |
NULL |
- |
Fecha lectura |
is_archived |
BOOLEAN |
NOT NULL |
false |
Archivada |
expires_at |
TIMESTAMPTZ |
NULL |
- |
Expiracion |
created_at |
TIMESTAMPTZ |
NOT NULL |
NOW() |
Fecha creacion |
CREATE TABLE core_notifications.notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
user_id UUID NOT NULL,
type VARCHAR(50) NOT NULL,
title VARCHAR(255) NOT NULL,
body TEXT NOT NULL,
data JSONB DEFAULT '{}',
action_url VARCHAR(500),
action_label VARCHAR(100),
icon VARCHAR(50),
channels VARCHAR(20)[] NOT NULL DEFAULT '{in_app}',
is_read BOOLEAN NOT NULL DEFAULT false,
read_at TIMESTAMPTZ,
is_archived BOOLEAN NOT NULL DEFAULT false,
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT fk_notifications_tenant
FOREIGN KEY (tenant_id) REFERENCES core_tenants.tenants(id) ON DELETE CASCADE,
CONSTRAINT fk_notifications_user
FOREIGN KEY (user_id) REFERENCES core_users.users(id) ON DELETE CASCADE
);
-- Indices
CREATE INDEX idx_notifications_user ON core_notifications.notifications(user_id, created_at DESC);
CREATE INDEX idx_notifications_user_unread ON core_notifications.notifications(user_id, is_read)
WHERE is_read = false;
CREATE INDEX idx_notifications_type ON core_notifications.notifications(type, created_at DESC);
CREATE INDEX idx_notifications_expires ON core_notifications.notifications(expires_at)
WHERE expires_at IS NOT NULL;
-- RLS
ALTER TABLE core_notifications.notifications ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON core_notifications.notifications
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
CREATE POLICY user_own ON core_notifications.notifications
FOR SELECT USING (user_id = current_setting('app.current_user_id')::uuid);
2. email_templates
Templates de email reutilizables.
| Columna |
Tipo |
Nullable |
Default |
Descripcion |
id |
UUID |
NOT NULL |
gen_random_uuid() |
PK |
tenant_id |
UUID |
NULL |
- |
NULL = global |
key |
VARCHAR(100) |
NOT NULL |
- |
Clave unica |
name |
VARCHAR(255) |
NOT NULL |
- |
Nombre descriptivo |
subject |
VARCHAR(255) |
NOT NULL |
- |
Asunto (con variables) |
body_html |
TEXT |
NOT NULL |
- |
Cuerpo HTML |
body_text |
TEXT |
NULL |
- |
Cuerpo texto plano |
variables |
JSONB |
NOT NULL |
'[]' |
Variables disponibles |
category |
VARCHAR(50) |
NOT NULL |
'transactional' |
Categoria |
is_active |
BOOLEAN |
NOT NULL |
true |
Activo |
created_at |
TIMESTAMPTZ |
NOT NULL |
NOW() |
Fecha creacion |
updated_at |
TIMESTAMPTZ |
NOT NULL |
NOW() |
Fecha actualizacion |
CREATE TABLE core_notifications.email_templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID,
key VARCHAR(100) NOT NULL,
name VARCHAR(255) NOT NULL,
subject VARCHAR(255) NOT NULL,
body_html TEXT NOT NULL,
body_text TEXT,
variables JSONB NOT NULL DEFAULT '[]',
category VARCHAR(50) NOT NULL DEFAULT 'transactional',
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uk_email_templates_key UNIQUE (tenant_id, key),
CONSTRAINT chk_category CHECK (category IN (
'transactional', 'marketing', 'digest', 'alert', 'welcome'
))
);
CREATE INDEX idx_email_templates_tenant ON core_notifications.email_templates(tenant_id);
CREATE INDEX idx_email_templates_category ON core_notifications.email_templates(category);
3. email_jobs
Cola de trabajos de email.
| Columna |
Tipo |
Nullable |
Default |
Descripcion |
id |
UUID |
NOT NULL |
gen_random_uuid() |
PK |
tenant_id |
UUID |
NOT NULL |
- |
FK a tenants |
template_id |
UUID |
NULL |
- |
FK a email_templates |
to_email |
VARCHAR(255) |
NOT NULL |
- |
Destinatario |
to_name |
VARCHAR(255) |
NULL |
- |
Nombre destinatario |
cc |
VARCHAR(255)[] |
NULL |
- |
Copia |
bcc |
VARCHAR(255)[] |
NULL |
- |
Copia oculta |
subject |
VARCHAR(255) |
NOT NULL |
- |
Asunto renderizado |
body_html |
TEXT |
NOT NULL |
- |
Cuerpo HTML renderizado |
body_text |
TEXT |
NULL |
- |
Cuerpo texto |
variables |
JSONB |
NULL |
- |
Variables usadas |
attachments |
JSONB |
NULL |
'[]' |
Adjuntos |
status |
VARCHAR(20) |
NOT NULL |
'pending' |
Estado |
priority |
INTEGER |
NOT NULL |
5 |
Prioridad (1-10) |
attempts |
INTEGER |
NOT NULL |
0 |
Intentos realizados |
max_attempts |
INTEGER |
NOT NULL |
3 |
Intentos maximos |
error |
TEXT |
NULL |
- |
Ultimo error |
scheduled_at |
TIMESTAMPTZ |
NOT NULL |
NOW() |
Programado para |
sent_at |
TIMESTAMPTZ |
NULL |
- |
Fecha envio |
opened_at |
TIMESTAMPTZ |
NULL |
- |
Fecha apertura |
clicked_at |
TIMESTAMPTZ |
NULL |
- |
Fecha click |
created_at |
TIMESTAMPTZ |
NOT NULL |
NOW() |
Fecha creacion |
CREATE TABLE core_notifications.email_jobs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
template_id UUID,
to_email VARCHAR(255) NOT NULL,
to_name VARCHAR(255),
cc VARCHAR(255)[],
bcc VARCHAR(255)[],
subject VARCHAR(255) NOT NULL,
body_html TEXT NOT NULL,
body_text TEXT,
variables JSONB,
attachments JSONB DEFAULT '[]',
status VARCHAR(20) NOT NULL DEFAULT 'pending',
priority INTEGER NOT NULL DEFAULT 5,
attempts INTEGER NOT NULL DEFAULT 0,
max_attempts INTEGER NOT NULL DEFAULT 3,
error TEXT,
scheduled_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
sent_at TIMESTAMPTZ,
opened_at TIMESTAMPTZ,
clicked_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT fk_email_jobs_template
FOREIGN KEY (template_id) REFERENCES core_notifications.email_templates(id),
CONSTRAINT chk_status CHECK (status IN (
'pending', 'processing', 'sent', 'failed', 'cancelled'
)),
CONSTRAINT chk_priority CHECK (priority BETWEEN 1 AND 10)
);
-- Indices para procesamiento de cola
CREATE INDEX idx_email_jobs_pending ON core_notifications.email_jobs(scheduled_at, priority DESC)
WHERE status = 'pending';
CREATE INDEX idx_email_jobs_status ON core_notifications.email_jobs(status, created_at);
CREATE INDEX idx_email_jobs_tenant ON core_notifications.email_jobs(tenant_id, created_at DESC);
4. push_subscriptions
Dispositivos registrados para push notifications.
| Columna |
Tipo |
Nullable |
Default |
Descripcion |
id |
UUID |
NOT NULL |
gen_random_uuid() |
PK |
user_id |
UUID |
NOT NULL |
- |
FK a users |
endpoint |
VARCHAR(500) |
NOT NULL |
- |
URL endpoint |
p256dh |
VARCHAR(255) |
NOT NULL |
- |
Public key |
auth |
VARCHAR(255) |
NOT NULL |
- |
Auth secret |
device_type |
VARCHAR(20) |
NOT NULL |
- |
web/android/ios |
device_name |
VARCHAR(100) |
NULL |
- |
Nombre dispositivo |
browser |
VARCHAR(50) |
NULL |
- |
Browser (para web) |
is_active |
BOOLEAN |
NOT NULL |
true |
Activa |
last_used_at |
TIMESTAMPTZ |
NULL |
- |
Ultimo uso |
created_at |
TIMESTAMPTZ |
NOT NULL |
NOW() |
Fecha registro |
CREATE TABLE core_notifications.push_subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
endpoint VARCHAR(500) NOT NULL,
p256dh VARCHAR(255) NOT NULL,
auth VARCHAR(255) NOT NULL,
device_type VARCHAR(20) NOT NULL,
device_name VARCHAR(100),
browser VARCHAR(50),
is_active BOOLEAN NOT NULL DEFAULT true,
last_used_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uk_push_subscriptions_endpoint UNIQUE (endpoint),
CONSTRAINT fk_push_subscriptions_user
FOREIGN KEY (user_id) REFERENCES core_users.users(id) ON DELETE CASCADE,
CONSTRAINT chk_device_type CHECK (device_type IN ('web', 'android', 'ios'))
);
CREATE INDEX idx_push_subscriptions_user ON core_notifications.push_subscriptions(user_id)
WHERE is_active = true;
5. push_jobs
Cola de trabajos de push notification.
| Columna |
Tipo |
Nullable |
Default |
Descripcion |
id |
UUID |
NOT NULL |
gen_random_uuid() |
PK |
subscription_id |
UUID |
NOT NULL |
- |
FK a push_subscriptions |
title |
VARCHAR(255) |
NOT NULL |
- |
Titulo |
body |
TEXT |
NOT NULL |
- |
Mensaje |
icon |
VARCHAR(500) |
NULL |
- |
URL icono |
badge |
VARCHAR(500) |
NULL |
- |
URL badge |
image |
VARCHAR(500) |
NULL |
- |
URL imagen |
data |
JSONB |
NULL |
'{}' |
Datos payload |
action_url |
VARCHAR(500) |
NULL |
- |
URL al hacer click |
status |
VARCHAR(20) |
NOT NULL |
'pending' |
Estado |
attempts |
INTEGER |
NOT NULL |
0 |
Intentos |
error |
TEXT |
NULL |
- |
Error |
sent_at |
TIMESTAMPTZ |
NULL |
- |
Fecha envio |
created_at |
TIMESTAMPTZ |
NOT NULL |
NOW() |
Fecha creacion |
CREATE TABLE core_notifications.push_jobs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
subscription_id UUID NOT NULL,
title VARCHAR(255) NOT NULL,
body TEXT NOT NULL,
icon VARCHAR(500),
badge VARCHAR(500),
image VARCHAR(500),
data JSONB DEFAULT '{}',
action_url VARCHAR(500),
status VARCHAR(20) NOT NULL DEFAULT 'pending',
attempts INTEGER NOT NULL DEFAULT 0,
error TEXT,
sent_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT fk_push_jobs_subscription
FOREIGN KEY (subscription_id) REFERENCES core_notifications.push_subscriptions(id) ON DELETE CASCADE,
CONSTRAINT chk_status CHECK (status IN ('pending', 'processing', 'sent', 'failed'))
);
CREATE INDEX idx_push_jobs_pending ON core_notifications.push_jobs(created_at)
WHERE status = 'pending';
6. notification_preferences
Preferencias de notificacion por usuario.
| Columna |
Tipo |
Nullable |
Default |
Descripcion |
id |
UUID |
NOT NULL |
gen_random_uuid() |
PK |
user_id |
UUID |
NOT NULL |
- |
FK a users |
notification_type |
VARCHAR(50) |
NOT NULL |
- |
Tipo de notificacion |
in_app |
BOOLEAN |
NOT NULL |
true |
Recibir in-app |
email |
BOOLEAN |
NOT NULL |
true |
Recibir email |
push |
BOOLEAN |
NOT NULL |
true |
Recibir push |
email_frequency |
VARCHAR(20) |
NOT NULL |
'instant' |
Frecuencia email |
created_at |
TIMESTAMPTZ |
NOT NULL |
NOW() |
Fecha creacion |
updated_at |
TIMESTAMPTZ |
NOT NULL |
NOW() |
Fecha actualizacion |
CREATE TABLE core_notifications.notification_preferences (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
notification_type VARCHAR(50) NOT NULL,
in_app BOOLEAN NOT NULL DEFAULT true,
email BOOLEAN NOT NULL DEFAULT true,
push BOOLEAN NOT NULL DEFAULT true,
email_frequency VARCHAR(20) NOT NULL DEFAULT 'instant',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uk_preferences_user_type UNIQUE (user_id, notification_type),
CONSTRAINT fk_preferences_user
FOREIGN KEY (user_id) REFERENCES core_users.users(id) ON DELETE CASCADE,
CONSTRAINT chk_email_frequency CHECK (email_frequency IN (
'instant', 'hourly', 'daily', 'weekly', 'never'
))
);
CREATE INDEX idx_preferences_user ON core_notifications.notification_preferences(user_id);
Tipos de Notificacion
-- Tipos de notificacion predefinidos
CREATE TYPE core_notifications.notification_type AS ENUM (
-- Seguridad
'security.login_new_device',
'security.password_changed',
'security.session_expired',
-- Sistema
'system.maintenance',
'system.update',
'system.announcement',
-- Usuarios
'user.mention',
'user.assignment',
'user.invitation',
-- Tareas
'task.assigned',
'task.completed',
'task.due_soon',
'task.overdue',
-- Documentos
'document.shared',
'document.comment',
'document.approved',
'document.rejected',
-- Pagos
'payment.received',
'payment.failed',
'payment.due',
-- Reportes
'report.ready',
'report.scheduled'
);
Funciones de Utilidad
Enviar Notificacion
CREATE OR REPLACE FUNCTION core_notifications.send_notification(
p_tenant_id UUID,
p_user_id UUID,
p_type VARCHAR,
p_title VARCHAR,
p_body TEXT,
p_data JSONB DEFAULT '{}',
p_action_url VARCHAR DEFAULT NULL,
p_channels VARCHAR[] DEFAULT '{in_app}'
) RETURNS UUID AS $$
DECLARE
v_notification_id UUID;
v_preferences RECORD;
v_effective_channels VARCHAR[];
BEGIN
-- Obtener preferencias del usuario
SELECT * INTO v_preferences
FROM core_notifications.notification_preferences
WHERE user_id = p_user_id AND notification_type = p_type;
-- Aplicar preferencias a canales
v_effective_channels := ARRAY[]::VARCHAR[];
IF 'in_app' = ANY(p_channels) AND (v_preferences IS NULL OR v_preferences.in_app) THEN
v_effective_channels := array_append(v_effective_channels, 'in_app');
END IF;
IF 'email' = ANY(p_channels) AND (v_preferences IS NULL OR v_preferences.email) THEN
v_effective_channels := array_append(v_effective_channels, 'email');
END IF;
IF 'push' = ANY(p_channels) AND (v_preferences IS NULL OR v_preferences.push) THEN
v_effective_channels := array_append(v_effective_channels, 'push');
END IF;
-- Crear notificacion in-app si aplica
IF 'in_app' = ANY(v_effective_channels) THEN
INSERT INTO core_notifications.notifications (
tenant_id, user_id, type, title, body, data, action_url, channels
) VALUES (
p_tenant_id, p_user_id, p_type, p_title, p_body, p_data, p_action_url, v_effective_channels
) RETURNING id INTO v_notification_id;
END IF;
RETURN v_notification_id;
END;
$$ LANGUAGE plpgsql;
Marcar Como Leidas
CREATE OR REPLACE FUNCTION core_notifications.mark_as_read(
p_user_id UUID,
p_notification_ids UUID[] DEFAULT NULL
) RETURNS INTEGER AS $$
DECLARE
v_count INTEGER;
BEGIN
IF p_notification_ids IS NULL THEN
-- Marcar todas
UPDATE core_notifications.notifications
SET is_read = true, read_at = NOW()
WHERE user_id = p_user_id AND is_read = false;
ELSE
-- Marcar especificas
UPDATE core_notifications.notifications
SET is_read = true, read_at = NOW()
WHERE user_id = p_user_id AND id = ANY(p_notification_ids) AND is_read = false;
END IF;
GET DIAGNOSTICS v_count = ROW_COUNT;
RETURN v_count;
END;
$$ LANGUAGE plpgsql;
Obtener Conteo de No Leidas
CREATE OR REPLACE FUNCTION core_notifications.get_unread_count(
p_user_id UUID
) RETURNS JSONB AS $$
BEGIN
RETURN (
SELECT jsonb_build_object(
'total', COUNT(*),
'by_type', jsonb_object_agg(type, count)
)
FROM (
SELECT type, COUNT(*) as count
FROM core_notifications.notifications
WHERE user_id = p_user_id AND is_read = false
GROUP BY type
) counts
);
END;
$$ LANGUAGE plpgsql STABLE;
Seed Data
Email Templates
INSERT INTO core_notifications.email_templates (tenant_id, key, name, subject, body_html, variables, category) VALUES
(NULL, 'welcome', 'Bienvenida',
'Bienvenido a {{app_name}}, {{user_name}}!',
'<h1>Hola {{user_name}}</h1><p>Gracias por registrarte en {{app_name}}.</p>',
'[{"name": "user_name", "required": true}, {"name": "app_name", "required": true}]',
'welcome'),
(NULL, 'password_reset', 'Restablecer Contrasena',
'Solicitud de restablecimiento de contrasena',
'<p>Haz click en el siguiente enlace para restablecer tu contrasena:</p><a href="{{reset_url}}">Restablecer</a>',
'[{"name": "reset_url", "required": true}]',
'transactional'),
(NULL, 'email_verification', 'Verificar Email',
'Verifica tu direccion de email',
'<p>Haz click para verificar tu email:</p><a href="{{verify_url}}">Verificar</a>',
'[{"name": "verify_url", "required": true}]',
'transactional'),
(NULL, 'invoice', 'Factura',
'Factura #{{invoice_number}}',
'<p>Adjuntamos la factura #{{invoice_number}} por {{amount}}.</p>',
'[{"name": "invoice_number", "required": true}, {"name": "amount", "required": true}]',
'transactional'),
(NULL, 'security_alert', 'Alerta de Seguridad',
'Alerta de seguridad: {{alert_title}}',
'<p>Se ha detectado una actividad sospechosa:</p><p>{{alert_description}}</p>',
'[{"name": "alert_title", "required": true}, {"name": "alert_description", "required": true}]',
'alert');
Mantenimiento
Limpiar Notificaciones Antiguas
CREATE OR REPLACE FUNCTION core_notifications.cleanup_old_notifications(
p_days_read INTEGER DEFAULT 30,
p_days_unread INTEGER DEFAULT 90
) RETURNS INTEGER AS $$
DECLARE
v_deleted INTEGER := 0;
BEGIN
-- Eliminar leidas antiguas
DELETE FROM core_notifications.notifications
WHERE is_read = true
AND read_at < NOW() - (p_days_read || ' days')::INTERVAL;
GET DIAGNOSTICS v_deleted = ROW_COUNT;
-- Eliminar no leidas muy antiguas
DELETE FROM core_notifications.notifications
WHERE is_read = false
AND created_at < NOW() - (p_days_unread || ' days')::INTERVAL;
v_deleted := v_deleted + ROW_COUNT;
-- Eliminar expiradas
DELETE FROM core_notifications.notifications
WHERE expires_at IS NOT NULL AND expires_at < NOW();
v_deleted := v_deleted + ROW_COUNT;
RETURN v_deleted;
END;
$$ LANGUAGE plpgsql;
Limpiar Jobs Completados
CREATE OR REPLACE FUNCTION core_notifications.cleanup_completed_jobs(
p_days INTEGER DEFAULT 7
) RETURNS JSONB AS $$
DECLARE
v_email_deleted INTEGER;
v_push_deleted INTEGER;
BEGIN
DELETE FROM core_notifications.email_jobs
WHERE status IN ('sent', 'cancelled')
AND sent_at < NOW() - (p_days || ' days')::INTERVAL;
GET DIAGNOSTICS v_email_deleted = ROW_COUNT;
DELETE FROM core_notifications.push_jobs
WHERE status = 'sent'
AND sent_at < NOW() - (p_days || ' days')::INTERVAL;
GET DIAGNOSTICS v_push_deleted = ROW_COUNT;
RETURN jsonb_build_object(
'email_jobs_deleted', v_email_deleted,
'push_jobs_deleted', v_push_deleted
);
END;
$$ LANGUAGE plpgsql;
Historial
| Version |
Fecha |
Autor |
Cambios |
| 1.0 |
2025-12-05 |
Requirements-Analyst |
Creacion inicial |