Saltar a contenido

Ofertas

La distinción conceptual más importante del modelo:

Plantilla y publicación no son lo mismo:

  • Lo que el comercio configura una vez y reutiliza.
  • No tiene stock, ni fecha, ni estado de publicación.
  • La instancia concreta de un día y una ventana.
  • Tiene stock propio, precio congelado y estado propio.
  • Es lo que el usuario ve y reserva.

Confundirlas haría imposible cambiar el precio de mañana sin reescribir el comprobante de ayer, que es justamente lo que el snapshot obligatorio evita.

También viven aquí plantilla_programacion y excepcion_calendario: ambas dependen de plantilla_oferta, así que documentarlas junto a Horarios formaría una dependencia circular entre módulos.

Requerimientos cubiertos

Alérgenos

Este módulo no almacena alérgenos como dato estructurado. La declaración corresponde al comercio dentro de la descripción de la oferta, que es donde el requerimiento de la oferta específica la exige.


plantilla_oferta

Configuración reutilizable de una oferta. Máximo cinco activas por comercio.

Columnas

Campo Tipo Nulo Predeterminado Descripción
comercio_id uuid no Llave foránea RESTRICT
nombre varchar(60) no
tipo varchar(20) no pack_sorpresa
oferta_especifica
categoria_comida_id uuid no Obligatoria incluso en pack sorpresa
descripcion text no '' Vacía en pack sorpresa
foto_id uuid sí Obligatoria en oferta específica
precio numeric(10,2) no
valor_referencia numeric(10,2) no
cantidad_default smallint no 1-50
is_active boolean no true Nunca se borra: hay publicaciones que la referencian
creada_by uuid no Llave foránea RESTRICT a usuario

Llaves foráneas

Columna Referencia ON DELETE Notas
comercio_id comercio RESTRICT
categoria_comida_id categoria_comida RESTRICT Catálogo administrable
foto_id media RESTRICT Las publicaciones la congelan

Constraints

CONSTRAINT chk_plantillaoferta_nombre_longitud CHECK (length(nombre) >= 1)

CONSTRAINT chk_plantillaoferta_variante
CHECK (
  (tipo = 'pack_sorpresa' AND descripcion = '' AND foto_id IS NULL)
  OR
  (
    tipo = 'oferta_especifica'
    AND length(descripcion) BETWEEN 20 AND 200
    AND foto_id IS NOT NULL
  )
)

CONSTRAINT chk_plantillaoferta_precio CHECK (precio > 0)
CONSTRAINT chk_plantillaoferta_valor_referencia CHECK (valor_referencia > precio)
CONSTRAINT chk_plantillaoferta_cantidad_default CHECK (cantidad_default BETWEEN 1 AND 50)

chk_plantillaoferta_variante reúne en una sola restricción las reglas del pack sorpresa y de la oferta específica. Está expresada como disyunción de dos variantes completas, y no como una lista de CHECK condicionales, para que el mensaje de error identifique la variante violada en lugar de una regla suelta.

Unicidad

Nombre Definición Propósito
unq_plantillaoferta_comercio_nombre_isactive (comercio_id, lower(nombre)) WHERE is_active Dos plantillas activas homónimas confundirían al operador

Índices

Nombre Definición Propósito
idx_plantillaoferta_comercio (comercio_id) WHERE is_active Panel de plantillas y conteo del tope

Es el índice de apoyo de trg_plantillaoferta_maximo_activas.

Triggers

Nombre Evento Momento Nivel Regla Origen
trg_plantillaoferta_maximo_activas INSERT
UPDATE
BEFORE ROW Plantillas activas del comercio dentro de plantilla.max_activas RF-C-27
trg_plantillaoferta_precio INSERT
UPDATE
BEFORE ROW precio >= precio.minimo y descuento sobre precio.descuento_minimo_pct RF-C-30
trg_plantillaoferta_items UPDATE BEFORE ROW Un pack sorpresa activo tiene entre 3 y 8 ítems; una oferta específica, ninguno RF-C-28

chk_plantillaoferta_valor_referencia ya exige que el valor de referencia supere al precio, pero no cuánto. El descuento mínimo del treinta por ciento que fija RF-C-30 es configurable por RF-A-41, así que no cabe en un CHECK: lo lee trg_plantillaoferta_precio desde parametro_sistema, junto con el precio mínimo.

trg_plantillaoferta_items es el que traduce "una plantilla de este tipo no puede activarse con menos de tres elementos en su lista" (RF-C-28). Cuenta filas hijas, así que es trigger, y corre al activar y no al crear: una plantilla en construcción puede estar incompleta mientras is_active sea falso.


plantilla_oferta_item

Lista "puede contener" del pack sorpresa.

Columnas

Campo Tipo Nulo Descripción
plantilla_oferta_id uuid no Llave foránea CASCADE
texto varchar(60) no Elemento que el pack contiene
orden smallint no 0 por defecto; el orden que eligió el comercio

Llaves foráneas

Columna Referencia ON DELETE Notas
plantilla_oferta_id plantilla_oferta CASCADE Un elemento no significa nada fuera de su plantilla

Constraints

CONSTRAINT chk_plantillaofertaitem_texto_longitud CHECK (length(texto) >= 1)

Unicidad

Nombre Definición Propósito
unq_plantillaofertaitem_plantilla_orden (plantilla_oferta_id, orden) El orden que el comercio eligió, no el de inserción

plantilla_programacion

Programación semanal de cada plantilla de oferta. Es lo que alimenta la generación automática de publicaciones diarias.

Columnas

Campo Tipo Nulo Descripción
plantilla_oferta_id uuid no Llave foránea CASCADE
dia_semana smallint no 0-6
ventana_id uuid no Llave foránea RESTRICT
cantidad_default smallint no 1-50
is_active boolean no Si es falso, ese día no se genera publicación

ventana_id y cantidad_default son siempre obligatorias, incluso cuando is_active es falso: desactivar un día no borra su configuración, solo suspende la generación.

Llaves foráneas

Columna Referencia ON DELETE Notas
plantilla_oferta_id plantilla_oferta CASCADE Es un fragmento de la plantilla, sin identidad propia
ventana_id ventana_recogida RESTRICT La ventana tiene vida propia y la comparten varias plantillas

Constraints

CONSTRAINT chk_plantillaprogramacion_diasemana CHECK (dia_semana BETWEEN 0 AND 6)

CONSTRAINT chk_plantillaprogramacion_cantidad_default
CHECK (cantidad_default BETWEEN 1 AND 50)

Unicidad

Nombre Definición
unq_plantillaprogramacion_plantilla_dia_ventana (plantilla_oferta_id, dia_semana, ventana_id)

Índices

Nombre Definición
idx_plantillaprogramacion_diasemana (dia_semana) WHERE is_active

Lo recorre la generación diaria de publicaciones.

Triggers

Nombre Evento Momento Nivel Regla Origen
trg_plantillaprogramacion_ventana INSERT
UPDATE
BEFORE ROW La ventana debe ser del mismo comercio que la plantilla y del mismo dia_semana RF-C-19

RF-C-19 lo enuncia palabra por palabra: "la ventana asignada debe pertenecer al mismo comercio y al mismo día de la semana". Son dos columnas de ventana_recogida y una de plantilla_oferta, tres tablas en juego, así que no hay CHECK ni llave foránea compuesta que lo exprese sin inventar índices redundantes en los dos padres.

Sin él, la generación diaria produce publicaciones cuya ventana pertenece a otro comercio o a otro día, y los cinco instantes que trg_publicacion_instantes deriva de esa ventana quedan resueltos contra horas que no corresponden a nada.


excepcion_calendario

Excepciones por fecha concreta, sin alterar la programación semanal.

Columnas

Campo Tipo Nulo Descripción
plantilla_oferta_id uuid no Llave foránea CASCADE
fecha date no Fecha operativa afectada
tipo varchar(20) no cerrado
modifica_ventana
modifica_cantidad
ventana_id uuid sí Requerida solo si modifica_ventana
cantidad smallint sí Requerida solo si modifica_cantidad

Llaves foráneas

Columna Referencia ON DELETE Notas
plantilla_oferta_id plantilla_oferta CASCADE
ventana_id ventana_recogida RESTRICT Solo si modifica_ventana

Constraints

CONSTRAINT chk_excepcioncalendario_variante
CHECK (
  (tipo = 'cerrado' AND ventana_id IS NULL AND cantidad IS NULL)
  OR
  (tipo = 'modifica_ventana' AND ventana_id IS NOT NULL AND cantidad IS NULL)
  OR
  (tipo = 'modifica_cantidad' AND ventana_id IS NULL AND cantidad IS NOT NULL)
)

CONSTRAINT chk_excepcioncalendario_cantidad
CHECK (cantidad IS NULL OR cantidad BETWEEN 1 AND 50)

Unicidad

Nombre Definición Propósito
unq_excepcioncalendario_plantilla_fecha_tipo (plantilla_oferta_id, fecha, tipo) Sin dos excepciones del mismo tipo por fecha

Puede haber más de una excepción para la misma fecha si son de tipo distinto (una modifica_ventana y otra modifica_cantidad el mismo día), pero no dos del mismo tipo.

Triggers

Nombre Evento Momento Nivel Regla Origen
trg_excepcioncalendario_horizonte INSERT
UPDATE
BEFORE ROW fecha entre hoy y noventa días hacia adelante RF-C-21
trg_excepcioncalendario_cierre INSERT
UPDATE
BEFORE ROW Un cerrado excluye cualquier otra excepción de la misma fecha RF-C-21
trg_excepcioncalendario_ventana INSERT
UPDATE
BEFORE ROW La ventana sustituta debe ser del comercio y del día de la semana de fecha RF-C-21

trg_excepcioncalendario_cierre es la mitad de RF-C-21 que la unicidad no alcanza: "un cierre excluye cualquier otra excepción del mismo día". unq_excepcioncalendario_plantilla_fecha_tipo admite hoy una cerrado y una modifica_cantidad sobre la misma fecha, que es una contradicción, porque un día cerrado no tiene cantidad que modificar. La regla mira filas hermanas, así que es trigger, y corre en las dos direcciones: rechaza un cerrado si ya hay otras excepciones ese día, y rechaza cualquier otra si ya hay un cerrado.

trg_excepcioncalendario_horizonte no puede ser CHECK porque compara contra hz_hoy(), que no es inmutable. El límite de noventa días es el que RF-C-21 fija de forma fija, no configurable, así que no es regla parametrizada.


publicacion

La instancia diaria. Es la tabla más leída del sistema.

Columnas

Campo Tipo Nulo Descripción
comercio_id uuid no Denormalizado: evita una unión en cada consulta del mapa
plantilla_oferta_id uuid sí Nulo si la publicación fue manual
ventana_id uuid no
fecha_operativa date no Fecha local
estado_id uuid no Llave foránea a estado_publicacion
origen varchar(12) no automatica
manual
duplicada
tipo varchar(20) no Snapshot de la plantilla
nombre varchar(60) no Snapshot
descripcion text no Snapshot
categoria_comida_id uuid no Snapshot
foto_id uuid sí Snapshot
precio numeric(10,2) no Snapshot
valor_referencia numeric(10,2) no Snapshot
descuento_pct numeric(6,2) no Generada almacenada
cantidad_total smallint no 0-50
cantidad_reservada smallint no default 0, mutada por ajustar_stock
cantidad_entregada smallint no default 0, mutada por registrar_entrega
cantidad_disponible smallint no Generada virtual
reservable_desde_at timestamptz no
reservas_cierran_at timestamptz no
ventana_inicio_at timestamptz no
ventana_fin_at timestamptz no
validacion_hasta_at timestamptz no
publicada_at timestamptz sí Marca de apertura de reservas
agotado_at timestamptz sí
cerrada_at timestamptz sí Marca de cierre
aptitud_declarada_at timestamptz sí RF-C-35
aptitud_declarada_by uuid sí Llave foránea RESTRICT a usuario_comercio
duplicada_de_id uuid sí Autorreferencia RESTRICT
creada_by uuid sí Nulo si la generó el proceso programado

Columnas generadas

descuento_pct numeric(6,2)
GENERATED ALWAYS AS (100 * (1 - precio / valor_referencia)) STORED

cantidad_disponible smallint
GENERATED ALWAYS AS (cantidad_total - cantidad_reservada) VIRTUAL

descuento_pct es almacenada porque la moderación de contenido y la alerta de descuento excesivo la consultan por umbral. cantidad_disponible es virtual porque nunca se indexa: cualquier consulta que la necesite usa cantidad_reservada < cantidad_total directamente.

Llaves foráneas

Columna Referencia ON DELETE Notas
estado_id estado_publicacion RESTRICT
comercio_id comercio RESTRICT Denormalizado, escrito una sola vez
plantilla_oferta_id plantilla_oferta RESTRICT
ventana_id ventana_recogida RESTRICT
categoria_comida_id categoria_comida RESTRICT
foto_id media RESTRICT La foto congelada no puede desaparecer
duplicada_de_id publicacion RESTRICT
aptitud_declarada_by usuario_comercio RESTRICT

Constraints

CONSTRAINT chk_publicacion_stock_coherente
CHECK (cantidad_reservada >= 0 AND cantidad_reservada <= cantidad_total)

CONSTRAINT chk_publicacion_entregado_coherente
CHECK (cantidad_entregada >= 0 AND cantidad_entregada <= cantidad_reservada)

CONSTRAINT chk_publicacion_orden_temporal
CHECK (
  reservable_desde_at <= reservas_cierran_at
  AND reservas_cierran_at <= ventana_inicio_at
  AND ventana_inicio_at <= ventana_fin_at
  AND ventana_fin_at <= validacion_hasta_at
)

CONSTRAINT chk_publicacion_publicada_con_aptitud
CHECK (publicada_at IS NULL OR aptitud_declarada_at IS NOT NULL)

CONSTRAINT chk_publicacion_variante
CHECK (
  (tipo = 'pack_sorpresa' AND descripcion = '' AND foto_id IS NULL)
  OR
  (
    tipo = 'oferta_especifica'
    AND length(descripcion) BETWEEN 20 AND 200
    AND foto_id IS NOT NULL
  )
)

CONSTRAINT chk_publicacion_precio_positivo CHECK (precio > 0)
CONSTRAINT chk_publicacion_valor_referencia_mayor CHECK (valor_referencia > precio)
CONSTRAINT chk_publicacion_cantidad_total CHECK (cantidad_total BETWEEN 0 AND 50)
CONSTRAINT chk_publicacion_origen CHECK (origen IN ('automatica', 'manual', 'duplicada'))

CONSTRAINT chk_publicacion_origen_autor
CHECK ((origen = 'automatica') = (creada_by IS NULL))

CONSTRAINT chk_publicacion_origen_duplicada
CHECK ((origen = 'duplicada') = (duplicada_de_id IS NOT NULL))

CONSTRAINT chk_publicacion_orden_marcas
CHECK (
  (agotado_at IS NULL OR (publicada_at IS NOT NULL AND agotado_at >= publicada_at))
  AND (cerrada_at IS NULL OR (publicada_at IS NOT NULL AND cerrada_at >= publicada_at))
)

chk_publicacion_origen_autor traduce la única diferencia real entre los tres orígenes: RF-C-23 genera desde un proceso, que no tiene usuario, y RF-C-24 y RF-C-34 los crea una persona. Hoy creada_by admite nulo sin condición, de modo que una publicación manual puede quedar sin autor y la bitácora de RF-C-14 no tiene a quién nombrar.

chk_publicacion_stock_coherente es una de las invariantes críticas del sistema. Combinada con que trg_reserva_ajustar_stock mueve cantidad_reservada dentro de la misma transacción que crea la reserva, convierte la sobreventa en un error de transacción sin SELECT ... FOR UPDATE explícito y sin ventana de carrera entre la lectura y la escritura.

chk_publicacion_variante/_precio_positivo/_valor_referencia_mayor se repiten aquí, ya declaradas también en plantilla_oferta, porque una publicación manual (plantilla_oferta_id IS NULL) puede traer estos valores propios sin haber pasado nunca por una plantilla, y el ReadOnly de abajo solo impide modificarlos después de creados, no valida su forma al insertar.

Unicidad

Nombre Definición Propósito
unq_publicacion_dia (comercio_id, plantilla_oferta_id, fecha_operativa, ventana_id), NULLS NOT DISTINCT Idempotencia del generador diario

NULLS NOT DISTINCT es lo que hace idempotente a generar_publicaciones_del_dia incluso cuando plantilla_oferta_id es nulo (publicación manual): sin él, dos publicaciones manuales del mismo comercio, fecha y ventana no colisionarían. Es también lo que cumple el "no puede existir más de una publicación de la misma plantilla para la misma fecha y ventana" de RF-C-24 sin ninguna comprobación previa.

Triggers

Nombre Evento Momento Nivel Regla Origen
trg_readonly_snapshot UPDATE de los snapshot BEFORE ROW Los campos congelados son inmutables tras la creación D-15
trg_publicacion_comercio_apto INSERT BEFORE ROW El comercio puede publicar y no tiene una pausa vigente RF-C-22
trg_publicacion_ventana INSERT BEFORE ROW La ventana es del comercio, está activa y su dia_semana coincide con fecha_operativa RF-C-19
trg_publicacion_snapshot INSERT BEFORE ROW Copia tipo, nombre, descripcion, categoria_comida_id, foto_id, precio y valor_referencia desde la plantilla D-15
trg_publicacion_instantes INSERT BEFORE ROW Deriva los cinco instantes de la ventana y la configuración RF-C-25
trg_publicacion_horizonte INSERT BEFORE ROW fecha_operativa dentro de publicacion.horizonte_horas RF-C-24
trg_publicacion_precio INSERT
UPDATE
BEFORE ROW Precio, descuento y cantidad contra parametro_sistema RF-C-30
trg_publicacion_aptitud UPDATE OF aptitud_declarada_by BEFORE ROW Quien declara la aptitud es miembro activo del comercio RF-C-35
trg_publicacion_agotado UPDATE OF cantidad_* BEFORE ROW Mueve el estado entre publicada y agotada según la disponibilidad RF-C-32
trg_publicacion_alerta_moderacion INSERT
UPDATE
AFTER ROW Levanta alerta de moderación al cruzar un umbral de contenido RF-A-25

trg_readonly_snapshot cubre comercio_id, plantilla_oferta_id, ventana_id, fecha_operativa, origen, tipo, nombre, descripcion, categoria_comida_id, foto_id, precio, valor_referencia y los cinco instantes. Los campos mutables son estado_id, cantidad_total, cantidad_reservada, cantidad_entregada, publicada_at, agotado_at, cerrada_at y aptitud_declarada_at/_by.

cantidad_total queda deliberadamente fuera de esa lista: dejarla de solo lectura habría hecho imposibles tres requerimientos a la vez — RF-C-31 ("el sistema impide reducir la cantidad total por debajo de las unidades ya reservadas", que presupone que se puede reducir), RF-C-32 ("el comercio puede aumentar o reducir la cantidad durante la ventana") y RF-C-33 (marcar como agotado "iguala la cantidad total a las unidades ya reservadas"). Lo que la protege no es ReadOnly: chk_publicacion_stock_coherente ya impide bajarla por debajo de cantidad_reservada, que es exactamente el límite de RF-C-31, y trg_publicacion_agotado se encarga del efecto en el estado.

trg_publicacion_instantes

La pieza que hace honesto el snapshot temporal. Los cinco instantes son hoy datos de entrada: chk_publicacion_orden_temporal comprueba que estén ordenados entre sí, no que correspondan a la ventana que la fila referencia. El trigger los deriva y sobrescribe lo que haya mandado el llamador:

Columna Se resuelve como
ventana_inicio_at fecha_operativa + ventana.hora_inicio, en America/Managua
ventana_fin_at fecha_operativa + ventana.hora_fin, en America/Managua
validacion_hasta_at ventana_fin_at + validacion.tolerancia_min
reservas_cierran_at ventana_fin_at − publicacion.corte_reservas_min
reservable_desde_at now() si comercio.reservable_desde_generacion; si no, ventana_inicio_at − comercio.horas_antes_reservable

Es también la única instancia de la familia de derivación que consume parámetros, y donde D-16 se paga: la tolerancia y el corte se leen una vez, en la escritura, y quedan congelados en la fila. Un cambio posterior del parámetro no mueve las publicaciones ya creadas, que es lo que las notas de diseño de abajo describen como "el contrato con quien ya reservó".

trg_publicacion_agotado

RF-C-32 describe una transición que el sistema hace solo: "si la disponibilidad llega a cero, la publicación pasa a agotada; si vuelve a ser positiva, regresa a publicada". Los dos pares están declarados en transicion_publicacion con actor sistema, así que el trigger no inventa nada: calcula cantidad_total - cantidad_reservada y mueve estado_id, y trg_publicacion_transicion_valida lo valida como a cualquier otro cambio.

Corre BEFORE y en la misma sentencia que mueve el contador, que es lo que permite cumplir el "treinta segundos o menos" de RF-C-32 sin ningún proceso que barra publicaciones buscando ceros.

trg_publicacion_alerta_moderacion

RF-A-25 enumera cinco disparadores y cuatro de ellos dependen solo de esta fila y de parametro_sistema: descuento sobre precio.descuento_revision_pct, precio bajo precio.minimo, oferta específica sin foto y cantidad mayor a treinta. El quinto, la primera publicación de un comercio recién aprobado, se resuelve contando las publicaciones anteriores del comercio.

Es AFTER INSERT OR UPDATE e inserta en alerta con ON CONFLICT ON CONSTRAINT unq_alerta_abierta DO NOTHING, que es lo que traduce el "no se acumulan alertas idénticas sin atender sobre la misma publicación" del requerimiento a control de flujo en vez de a un error.

Índices

Nombre Definición Propósito
idx_publicacion_directorio (estado_id, comercio_id, fecha_operativa) Directorio y exploración de ofertas
idx_publicacion_cierre (estado_id, reservas_cierran_at) Barrido del proceso de cierre de ventana

Notas de diseño

Los cinco instantes se resuelven al crear la publicación y no se recalculan: si el comercio edita la ventana de recogida mañana, las publicaciones de hoy conservan los suyos, son el contrato con quien ya reservó.

comercio_id está denormalizado desde la plantilla aunque sea derivable. La consulta del directorio filtra y agrupa por comercio en cada carga, y trg_readonly_snapshot garantiza que la copia no pueda contradecir al original.


publicacion_item

Copia congelada de plantilla_oferta_item al publicar. Copia y no referencia porque el comprobante que el usuario consulta meses después debe mostrar lo que decía la oferta ese día.

Columnas

Campo Tipo Nulo Descripción
publicacion_id uuid no Llave foránea CASCADE
texto varchar(60) no Copia congelada
orden smallint no 0 por defecto; el orden en que el usuario vio la lista al reservar

Constraints

CONSTRAINT chk_publicacionitem_texto_longitud CHECK (length(texto) >= 1)

Unicidad

Ninguna, además de la llave primaria, y por la misma razón que plantilla_oferta_item: una unq_publicacionitem_publicacion limitaría a un ítem por publicación y haría imposible copiar la lista de tres a ocho elementos que la plantilla declara. La ficha anterior la documentaba; no existe en el código y no debe crearse.

orden importa aquí más que en la plantilla: el comprobante que el usuario consulta meses después debe mostrar la lista en el mismo orden en que la vio al reservar.


despublicacion

Retiro administrativo de una publicación. Relación 1:1.

Columnas

Campo Tipo Nulo Descripción
publicacion_id uuid no Llave foránea RESTRICT, única
motivo_id uuid no Llave foránea RESTRICT a motivo
nota text no ''
resolucion_reservas varchar(12) no mantener
cancelar
reservas_afectadas smallint no Contadas por un trigger
administrador_id uuid no Llave foránea RESTRICT a usuario
notificado_at timestamptz sí Aviso al comercio

Constraints

CONSTRAINT chk_despublicacion_resolucion
CHECK (resolucion_reservas IN ('mantener', 'cancelar'))

CONSTRAINT chk_despublicacion_reservas_afectadas CHECK (reservas_afectadas >= 0)

Unicidad

Nombre Definición Propósito
unq_despublicacion_publicacion (publicacion_id) Una publicación no se retira dos veces

Es la unicidad que cumple el "una publicación no puede despublicarse dos veces" de RF-A-24, sin ninguna comprobación previa.

Triggers

Nombre Evento Momento Nivel Regla Origen
trg_despublicacion_reservas INSERT BEFORE ROW Deriva reservas_afectadas contando las reservas vivas de la publicación RF-A-24
trg_despublicacion_estado INSERT AFTER ROW Mueve la publicación al estado despublicada RF-A-24

trg_despublicacion_reservas es lo que la ficha de columnas ya prometía al describir reservas_afectadas como "contadas por un trigger". Como toda la familia de derivación, sobrescribe en vez de comparar: el número tiene que ser el que la base contó en el instante de la despublicación, no el que declaró quien la ejecutó, porque es la evidencia de cuánta gente quedó afectada por una decisión administrativa.

trg_despublicacion_estado cierra el bucle: insertar la fila de despublicación es despublicar. Dejar el cambio de estado en manos del llamador permitiría una despublicación registrada que nunca retiró la oferta del mapa, que es lo contrario de lo que RF-A-24 llama "de forma inmediata".