Agrego carpeta del source sql de la Base de Datos
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
-- ============================================================================
|
||||
-- internal.app_config
|
||||
-- ============================================================================
|
||||
-- Configuración runtime del sistema. Pares clave/valor (ambos TEXT).
|
||||
-- Lee internal.get_config_int y fc_obtener_config.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE internal.app_config (
|
||||
clave text NOT NULL,
|
||||
valor text NOT NULL,
|
||||
descripcion text,
|
||||
actualizado_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT app_config_pkey PRIMARY KEY (clave)
|
||||
);
|
||||
@@ -0,0 +1,72 @@
|
||||
-- ============================================================================
|
||||
-- internal.archivo_exports
|
||||
-- ============================================================================
|
||||
-- Bitácora de control del pipeline mensual de archivado
|
||||
-- (Documentation/PoliticaRetencion.md §5.3). Cada fila registra un intento
|
||||
-- de archivar una entidad para un mes objetivo: su resultado, cuándo
|
||||
-- ocurrió y, si falló, el detalle del error.
|
||||
--
|
||||
-- Cumple tres roles:
|
||||
-- - IDEMPOTENCIA: el unique parcial garantiza que no haya dos archivados
|
||||
-- `ok` para el mismo (anio_mes_target, tipo). Si la corrida mensual se
|
||||
-- repite (reintento, doble disparo del cron), el segundo `ok` choca y
|
||||
-- no se duplica el archivado ni el DELETE posterior.
|
||||
-- - REINTENTO: una fila `fallo` deja registrado el mes pendiente; la
|
||||
-- corrida siguiente puede detectar que ese mes no tiene `ok` y
|
||||
-- reprocesarlo (los meses pendientes tienen prioridad — §5.2).
|
||||
-- - VISIBILIDAD: la vista admin (US-R21) lee esta tabla vía
|
||||
-- public.fc_obtener_estado_archivo.
|
||||
--
|
||||
-- COLUMNAS
|
||||
-- anio_mes_target DATE (día 1 del mes archivado). NULLABLE a propósito:
|
||||
-- durante el primer año de vida del sistema ningún pago
|
||||
-- cumple 12 meses, así que la corrida de pagos archiva
|
||||
-- "nada" y registra `ok` con target NULL (§4.3, US-R17).
|
||||
-- Los NULL son distintos entre sí en el unique parcial,
|
||||
-- de modo que esas corridas vacías no se bloquean entre
|
||||
-- ellas — son no-ops inofensivos.
|
||||
-- tipo 'pagos' | 'agregado_reservas'. Las dos cosas que se
|
||||
-- archivan (§3); cada una lleva su propia fila por mes.
|
||||
-- ejecutado_en timestamptz del intento (default now()).
|
||||
-- resultado 'ok' | 'fallo'.
|
||||
-- detalle_error texto libre del error cuando resultado = 'fallo';
|
||||
-- NULL en los `ok`.
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- Tabla en el schema `internal`, no alcanzable por roles cliente
|
||||
-- (hardening en 00_schemas.sql). Escribe el pipeline (US-R17); lee la
|
||||
-- fachada public.fc_obtener_estado_archivo (SECURITY DEFINER).
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- Nada (sin FKs). Nivel 0 en apply.sh.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE internal.archivo_exports (
|
||||
id integer GENERATED ALWAYS AS IDENTITY (
|
||||
SEQUENCE NAME internal.archivo_exports_id_seq
|
||||
START WITH 1 INCREMENT BY 1
|
||||
NO MINVALUE NO MAXVALUE CACHE 1
|
||||
) NOT NULL,
|
||||
anio_mes_target date,
|
||||
tipo text NOT NULL,
|
||||
ejecutado_en timestamp with time zone DEFAULT now() NOT NULL,
|
||||
resultado text NOT NULL,
|
||||
detalle_error text,
|
||||
CONSTRAINT archivo_exports_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT archivo_exports_tipo_check
|
||||
CHECK (tipo IN ('pagos', 'agregado_reservas')),
|
||||
CONSTRAINT archivo_exports_resultado_check
|
||||
CHECK (resultado IN ('ok', 'fallo'))
|
||||
);
|
||||
|
||||
-- Idempotencia: a lo sumo un `ok` por (mes, tipo). Parcial sobre 'ok' para
|
||||
-- que los reintentos fallidos no cuenten y puedan repetirse hasta lograrlo.
|
||||
CREATE UNIQUE INDEX archivo_exports_ok_unico
|
||||
ON internal.archivo_exports (anio_mes_target, tipo)
|
||||
WHERE resultado = 'ok';
|
||||
|
||||
-- Para listar fallos recientes y barrer pendientes por mes.
|
||||
CREATE INDEX idx_archivo_exports_target_tipo
|
||||
ON internal.archivo_exports (anio_mes_target, tipo, ejecutado_en DESC);
|
||||
|
||||
ALTER TABLE internal.archivo_exports ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,21 @@
|
||||
-- ============================================================================
|
||||
-- internal.permisos
|
||||
-- ============================================================================
|
||||
-- Matriz de autorización. Cada fila es una acción posible del sistema y
|
||||
-- el array de roles que la pueden ejecutar. La consulta canónica está en
|
||||
-- internal.validate_permission.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE internal.permisos (
|
||||
id integer GENERATED ALWAYS AS IDENTITY (
|
||||
SEQUENCE NAME internal.permisos_id_permiso_seq
|
||||
START WITH 1 INCREMENT BY 1
|
||||
NO MINVALUE NO MAXVALUE CACHE 1
|
||||
) NOT NULL,
|
||||
accion text NOT NULL,
|
||||
roles_permitidos text[] NOT NULL,
|
||||
CONSTRAINT permisos_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT permisos_accion_key UNIQUE (accion)
|
||||
);
|
||||
|
||||
ALTER TABLE internal.permisos ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,31 @@
|
||||
-- ============================================================================
|
||||
-- internal.sesiones
|
||||
-- ============================================================================
|
||||
-- Sesiones activas. Cada usuario tiene a lo sumo una sesión vigente; el
|
||||
-- trigger trg_clean_old_sessions (definido en triggers/auth/) lo garantiza
|
||||
-- al insertar.
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- public.usuarios
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE internal.sesiones (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
usuario_id uuid NOT NULL,
|
||||
token uuid NOT NULL,
|
||||
expires_at timestamp with time zone NOT NULL,
|
||||
CONSTRAINT sessions_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT sessions_usuario_id_fkey
|
||||
FOREIGN KEY (usuario_id) REFERENCES public.usuarios(id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_sesiones_token
|
||||
ON internal.sesiones USING btree (token);
|
||||
|
||||
CREATE INDEX idx_sesiones_token_expires
|
||||
ON internal.sesiones USING btree (token, expires_at);
|
||||
|
||||
CREATE INDEX idx_sesiones_usuario_expires
|
||||
ON internal.sesiones USING btree (usuario_id, expires_at);
|
||||
|
||||
ALTER TABLE internal.sesiones ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,31 @@
|
||||
-- ============================================================================
|
||||
-- public.actividades
|
||||
-- ============================================================================
|
||||
-- Entidades reservables del gimnasio (yoga, funcional, etc.). Los
|
||||
-- turnos son instancias concretas de una actividad en una fecha+hora.
|
||||
-- Cada actividad tiene duración, capacidad por defecto y un flag `libre`
|
||||
-- que indica si se puede reservar sin plan que la incluya.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.actividades (
|
||||
id integer GENERATED ALWAYS AS IDENTITY (
|
||||
SEQUENCE NAME public.actividades_id_seq
|
||||
START WITH 1 INCREMENT BY 1
|
||||
NO MINVALUE NO MAXVALUE CACHE 1
|
||||
) NOT NULL,
|
||||
nombre text NOT NULL,
|
||||
duracion smallint NOT NULL,
|
||||
capacidad_por_defecto smallint NOT NULL,
|
||||
libre boolean DEFAULT false,
|
||||
activo boolean DEFAULT true,
|
||||
CONSTRAINT actividades_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT actividades_nombre_key UNIQUE (nombre)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_actividades_activo
|
||||
ON public.actividades USING btree (activo, nombre);
|
||||
|
||||
CREATE INDEX idx_actividades_libre
|
||||
ON public.actividades USING btree (libre) WHERE (libre = true);
|
||||
|
||||
ALTER TABLE public.actividades ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,25 @@
|
||||
-- ============================================================================
|
||||
-- public.actividades_tipos_cuota
|
||||
-- ============================================================================
|
||||
-- Relación N:M entre tipos de cuota (planes) y actividades incluidas en
|
||||
-- ese plan. PK compuesta.
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- public.actividades, public.tipos_cuota
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.actividades_tipos_cuota (
|
||||
tipo_cuota_id uuid NOT NULL,
|
||||
actividad_id integer NOT NULL,
|
||||
CONSTRAINT actividades_tipos_cuota_pkey
|
||||
PRIMARY KEY (tipo_cuota_id, actividad_id),
|
||||
CONSTRAINT actividades_tipos_cuota_actividad_id_fkey
|
||||
FOREIGN KEY (actividad_id) REFERENCES public.actividades(id),
|
||||
CONSTRAINT actividades_tipos_cuota_tipo_cuota_id_fkey
|
||||
FOREIGN KEY (tipo_cuota_id) REFERENCES public.tipos_cuota(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_actividades_tipos_cuota_actividad
|
||||
ON public.actividades_tipos_cuota USING btree (actividad_id, tipo_cuota_id);
|
||||
|
||||
ALTER TABLE public.actividades_tipos_cuota ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,34 @@
|
||||
-- ============================================================================
|
||||
-- public.avisos_pago
|
||||
-- ============================================================================
|
||||
-- Avisos enviados a clientes relacionados a sus pagos / deudas. Estado
|
||||
-- limitado a 'enviado', 'bloqueado' o NULL.
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- public.usuarios
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.avisos_pago (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
cliente_id uuid,
|
||||
mensaje text,
|
||||
estado character varying(20) DEFAULT NULL::character varying,
|
||||
fecha_aviso timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT avisos_pago_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT avisos_pago_estado_check
|
||||
CHECK (((estado)::text = ANY (ARRAY[
|
||||
('enviado'::character varying)::text,
|
||||
('bloqueado'::character varying)::text,
|
||||
(NULL::character varying)::text
|
||||
]))),
|
||||
CONSTRAINT avisos_pago_cliente_id_fkey
|
||||
FOREIGN KEY (cliente_id) REFERENCES public.usuarios(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_avisos_pago_cliente_id
|
||||
ON public.avisos_pago USING btree (cliente_id);
|
||||
|
||||
CREATE INDEX idx_avisos_pago_estado_fecha
|
||||
ON public.avisos_pago USING btree (estado, fecha_aviso DESC) WHERE (estado IS NOT NULL);
|
||||
|
||||
ALTER TABLE public.avisos_pago ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,27 @@
|
||||
-- ============================================================================
|
||||
-- public.dias_especiales
|
||||
-- ============================================================================
|
||||
-- Días con un régimen distinto al regular: cerrado (no se generan turnos)
|
||||
-- o con horario diferente (los turnos vienen de horario_actividad_especial
|
||||
-- en lugar de horario_actividad).
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.dias_especiales (
|
||||
id integer GENERATED ALWAYS AS IDENTITY (
|
||||
SEQUENCE NAME public.dias_especiales_id_seq
|
||||
START WITH 1 INCREMENT BY 1
|
||||
NO MINVALUE NO MAXVALUE CACHE 1
|
||||
) NOT NULL,
|
||||
fecha date NOT NULL,
|
||||
tipo character varying(25) NOT NULL,
|
||||
motivo text,
|
||||
CONSTRAINT dias_especiales_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT dias_especiales_fecha_key UNIQUE (fecha),
|
||||
CONSTRAINT dias_especiales_tipo_check
|
||||
CHECK (((tipo)::text = ANY (ARRAY[
|
||||
('cerrado'::character varying)::text,
|
||||
('horario_diferente'::character varying)::text
|
||||
])))
|
||||
);
|
||||
|
||||
ALTER TABLE public.dias_especiales ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,49 @@
|
||||
-- ============================================================================
|
||||
-- public.eventos
|
||||
-- ============================================================================
|
||||
-- Tabla polimórfica de eventos auditables. Cada fila representa una
|
||||
-- acción que el sistema quiere preservar para auditoría: edición o
|
||||
-- anulación de pagos, futuras acciones administrativas, etc.
|
||||
--
|
||||
-- - tabla_referencia + referencia_id: a qué fila apunta el evento.
|
||||
-- - cliente_id: cliente afectado (puede ser NULL para eventos no
|
||||
-- relacionados a un cliente puntual).
|
||||
-- - actor_id: quién ejecutó la acción.
|
||||
-- - valor_anterior / valor_actual: snapshots JSONB para reconstruir el
|
||||
-- cambio (no necesariamente ambos llenos).
|
||||
-- - descripcion: texto libre (ej. motivo de anulación).
|
||||
--
|
||||
-- El único entry point oficial para escribir acá es internal.log_evento.
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- public.usuarios
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.eventos (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
tipo text NOT NULL,
|
||||
descripcion text,
|
||||
cliente_id uuid,
|
||||
valor_anterior jsonb,
|
||||
valor_actual jsonb,
|
||||
fecha_evento timestamp with time zone DEFAULT now(),
|
||||
referencia_id uuid,
|
||||
tabla_referencia text,
|
||||
actor_id uuid,
|
||||
CONSTRAINT eventos_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT eventos_actor_id_fkey
|
||||
FOREIGN KEY (actor_id) REFERENCES public.usuarios(id),
|
||||
CONSTRAINT eventos_cliente_id_fkey
|
||||
FOREIGN KEY (cliente_id) REFERENCES public.usuarios(id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_eventos_cliente_fecha
|
||||
ON public.eventos USING btree (cliente_id, fecha_evento DESC) WHERE (cliente_id IS NOT NULL);
|
||||
|
||||
CREATE INDEX idx_eventos_referencia
|
||||
ON public.eventos USING btree (tabla_referencia, referencia_id) WHERE (referencia_id IS NOT NULL);
|
||||
|
||||
CREATE INDEX idx_eventos_tipo_fecha
|
||||
ON public.eventos USING btree (tipo, fecha_evento DESC);
|
||||
|
||||
ALTER TABLE public.eventos ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,22 @@
|
||||
-- ============================================================================
|
||||
-- public.fotos_perfil
|
||||
-- ============================================================================
|
||||
-- Foto de perfil de un cliente. Una foto por cliente (UNIQUE cliente_id).
|
||||
-- Si se elimina el cliente, su foto se borra en cascada.
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- public.usuarios
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.fotos_perfil (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
cliente_id uuid,
|
||||
url_foto text,
|
||||
fecha_subida timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT fotos_perfil_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT fotos_perfil_cliente_id_key UNIQUE (cliente_id),
|
||||
CONSTRAINT fotos_perfil_cliente_id_fkey
|
||||
FOREIGN KEY (cliente_id) REFERENCES public.usuarios(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
ALTER TABLE public.fotos_perfil ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,48 @@
|
||||
-- ============================================================================
|
||||
-- public.horario_actividad
|
||||
-- ============================================================================
|
||||
-- Plantilla horaria regular: para cada (dia_semana, actividad_id), define
|
||||
-- los rangos horarios vigentes en una ventana de fechas (valido_desde,
|
||||
-- valido_hasta). valido_hasta NULL = vigencia abierta hacia el futuro.
|
||||
--
|
||||
-- El EXCLUDE constraint garantiza que no haya solapamiento entre rangos
|
||||
-- horarios de la misma actividad y día de la semana en vigencias que se
|
||||
-- intersectan. Requiere la extensión btree_gist y el type internal.timerange
|
||||
-- (definidos en 10_extensions_y_types.sql).
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- public.actividades; extension btree_gist; type internal.timerange.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.horario_actividad (
|
||||
id integer GENERATED ALWAYS AS IDENTITY (
|
||||
SEQUENCE NAME public.horario_actividad_id_seq
|
||||
START WITH 1 INCREMENT BY 1
|
||||
NO MINVALUE NO MAXVALUE CACHE 1
|
||||
) NOT NULL,
|
||||
actividad_id integer NOT NULL,
|
||||
dia_semana smallint NOT NULL,
|
||||
hora_inicio time without time zone NOT NULL,
|
||||
hora_fin time without time zone NOT NULL,
|
||||
valido_desde date DEFAULT CURRENT_DATE NOT NULL,
|
||||
valido_hasta date,
|
||||
CONSTRAINT horario_actividad_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT horario_actividad_check
|
||||
CHECK ((hora_inicio < hora_fin)),
|
||||
CONSTRAINT horario_actividad_dia_semana_check
|
||||
CHECK (((dia_semana >= 1) AND (dia_semana <= 7))),
|
||||
CONSTRAINT horario_actividad_vigencia_check
|
||||
CHECK (((valido_hasta IS NULL) OR (valido_desde <= valido_hasta))),
|
||||
CONSTRAINT horario_actividad_actividad_id_fkey
|
||||
FOREIGN KEY (actividad_id) REFERENCES public.actividades(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
ALTER TABLE public.horario_actividad
|
||||
ADD CONSTRAINT horario_actividad_no_solapamiento EXCLUDE USING gist (
|
||||
dia_semana WITH =,
|
||||
actividad_id WITH =,
|
||||
internal.timerange(hora_inicio, hora_fin) WITH &&,
|
||||
daterange(valido_desde, COALESCE(valido_hasta, 'infinity'::date), '[]'::text) WITH &&
|
||||
);
|
||||
|
||||
ALTER TABLE public.horario_actividad ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,31 @@
|
||||
-- ============================================================================
|
||||
-- public.horario_actividad_especial
|
||||
-- ============================================================================
|
||||
-- Rangos horarios para días marcados como horario_diferente en
|
||||
-- dias_especiales. Sustituyen la plantilla regular para esa fecha
|
||||
-- puntual.
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- public.actividades, public.dias_especiales
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.horario_actividad_especial (
|
||||
id integer GENERATED ALWAYS AS IDENTITY (
|
||||
SEQUENCE NAME public.horario_actividad_especial_id_seq
|
||||
START WITH 1 INCREMENT BY 1
|
||||
NO MINVALUE NO MAXVALUE CACHE 1
|
||||
) NOT NULL,
|
||||
dia_especial_id integer NOT NULL,
|
||||
actividad_id integer NOT NULL,
|
||||
hora_inicio time without time zone NOT NULL,
|
||||
hora_fin time without time zone NOT NULL,
|
||||
CONSTRAINT horario_actividad_especial_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT horario_actividad_especial_check_horas
|
||||
CHECK ((hora_inicio < hora_fin)),
|
||||
CONSTRAINT horario_actividad_especial_actividad_fkey
|
||||
FOREIGN KEY (actividad_id) REFERENCES public.actividades(id) ON DELETE CASCADE,
|
||||
CONSTRAINT horario_actividad_especial_dia_fkey
|
||||
FOREIGN KEY (dia_especial_id) REFERENCES public.dias_especiales(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
ALTER TABLE public.horario_actividad_especial ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,23 @@
|
||||
-- ============================================================================
|
||||
-- public.metodos_pago
|
||||
-- ============================================================================
|
||||
-- Métodos de cobro disponibles (efectivo, transferencia, billetera digital,
|
||||
-- etc.). Cada pago referencia uno acá. La baja es lógica via flag `activo`.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.metodos_pago (
|
||||
id integer GENERATED ALWAYS AS IDENTITY (
|
||||
SEQUENCE NAME public.metodos_pago_id_seq
|
||||
START WITH 1 INCREMENT BY 1
|
||||
NO MINVALUE NO MAXVALUE CACHE 1
|
||||
) NOT NULL,
|
||||
descripcion character varying NOT NULL,
|
||||
activo boolean DEFAULT true,
|
||||
icono character varying,
|
||||
CONSTRAINT metodos_pago_pkey PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_metodos_pago_activo
|
||||
ON public.metodos_pago USING btree (activo) WHERE (activo = true);
|
||||
|
||||
ALTER TABLE public.metodos_pago ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,29 @@
|
||||
-- ============================================================================
|
||||
-- public.notificaciones
|
||||
-- ============================================================================
|
||||
-- Notificaciones dirigidas a un cliente (push, in-app, etc.). Conserva
|
||||
-- flag de leída.
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- public.usuarios
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.notificaciones (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
cliente_id uuid,
|
||||
titulo character varying(200) NOT NULL,
|
||||
mensaje text,
|
||||
tipo character varying(50),
|
||||
leida boolean DEFAULT false,
|
||||
CONSTRAINT notificaciones_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT notificaciones_cliente_id_fkey
|
||||
FOREIGN KEY (cliente_id) REFERENCES public.usuarios(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_notificaciones_cliente_id
|
||||
ON public.notificaciones USING btree (cliente_id);
|
||||
|
||||
CREATE INDEX idx_notificaciones_cliente_leida
|
||||
ON public.notificaciones USING btree (cliente_id, leida, id DESC) WHERE (leida = false);
|
||||
|
||||
ALTER TABLE public.notificaciones ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,87 @@
|
||||
-- ============================================================================
|
||||
-- public.pagos
|
||||
-- ============================================================================
|
||||
-- Tabla central del módulo de pagos (ver Documentation/DominioPagos.md).
|
||||
-- Cada fila es un cobro recibido de un cliente. Soporta auditoría de
|
||||
-- creación, edición y anulación (anulación es soft-delete).
|
||||
--
|
||||
-- - tipo: discriminador del concepto del cobro. Hoy sólo se usa
|
||||
-- 'cuota_mensual'; el dominio admite 'devolucion',
|
||||
-- 'descuento_retroactivo', 'ajuste' pero no están implementados (ver
|
||||
-- Documentation/DominioPagos.md §10 — decisión cerrada de NO modelar
|
||||
-- correctivos por ahora).
|
||||
-- - pagos_anulacion_coherente: si anulado_at se setea, anulado_por
|
||||
-- también. motivo_anulacion es opcional.
|
||||
-- - pagos_update_coherente: si updated_at se setea, updated_by también.
|
||||
-- - pagos_campos_requeridos_por_tipo: para tipos distintos de 'ajuste',
|
||||
-- metodo_id y anio_mes_pagado son obligatorios.
|
||||
-- - pagos_anio_mes_pagado_check: anio_mes_pagado siempre es el día 1 del
|
||||
-- mes.
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- public.usuarios, public.metodos_pago, public.tipos_cuota
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.pagos (
|
||||
id uuid NOT NULL,
|
||||
cliente_id uuid,
|
||||
anio_mes_pagado date,
|
||||
fecha_pago timestamp with time zone,
|
||||
monto_total numeric,
|
||||
detalle jsonb,
|
||||
metodo_id smallint,
|
||||
tipo_cuota_id uuid,
|
||||
tipo text DEFAULT 'cuota_mensual'::text NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
created_by uuid,
|
||||
updated_at timestamp with time zone,
|
||||
updated_by uuid,
|
||||
anulado_at timestamp with time zone,
|
||||
anulado_por uuid,
|
||||
motivo_anulacion text,
|
||||
CONSTRAINT pagos_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT pagos_tipo_check
|
||||
CHECK ((tipo = ANY (ARRAY[
|
||||
'cuota_mensual'::text,
|
||||
'devolucion'::text,
|
||||
'descuento_retroactivo'::text,
|
||||
'ajuste'::text
|
||||
]))),
|
||||
CONSTRAINT pagos_anio_mes_pagado_check
|
||||
CHECK ((anio_mes_pagado = (date_trunc('month'::text, (anio_mes_pagado)::timestamp with time zone))::date)),
|
||||
CONSTRAINT pagos_campos_requeridos_por_tipo
|
||||
CHECK (((tipo = 'ajuste'::text) OR ((metodo_id IS NOT NULL) AND (anio_mes_pagado IS NOT NULL)))),
|
||||
CONSTRAINT pagos_anulacion_coherente
|
||||
CHECK ((((anulado_at IS NULL) AND (anulado_por IS NULL) AND (motivo_anulacion IS NULL))
|
||||
OR ((anulado_at IS NOT NULL) AND (anulado_por IS NOT NULL)))),
|
||||
CONSTRAINT pagos_update_coherente
|
||||
CHECK ((((updated_at IS NULL) AND (updated_by IS NULL))
|
||||
OR ((updated_at IS NOT NULL) AND (updated_by IS NOT NULL)))),
|
||||
CONSTRAINT pagos_cliente_id_fkey
|
||||
FOREIGN KEY (cliente_id) REFERENCES public.usuarios(id),
|
||||
CONSTRAINT pagos_metodo_id_fkey
|
||||
FOREIGN KEY (metodo_id) REFERENCES public.metodos_pago(id),
|
||||
CONSTRAINT pagos_tipo_cuota_id_fkey
|
||||
FOREIGN KEY (tipo_cuota_id) REFERENCES public.tipos_cuota(id),
|
||||
CONSTRAINT pagos_created_by_fkey
|
||||
FOREIGN KEY (created_by) REFERENCES public.usuarios(id),
|
||||
CONSTRAINT pagos_updated_by_fkey
|
||||
FOREIGN KEY (updated_by) REFERENCES public.usuarios(id),
|
||||
CONSTRAINT pagos_anulado_por_fkey
|
||||
FOREIGN KEY (anulado_por) REFERENCES public.usuarios(id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_pagos_cliente_no_anulado
|
||||
ON public.pagos USING btree (cliente_id, anio_mes_pagado DESC) WHERE (anulado_at IS NULL);
|
||||
|
||||
CREATE INDEX idx_pagos_cuota_mensual_no_anulado
|
||||
ON public.pagos USING btree (cliente_id, anio_mes_pagado DESC)
|
||||
WHERE ((anulado_at IS NULL) AND (tipo = 'cuota_mensual'::text));
|
||||
|
||||
CREATE INDEX idx_pagos_metodo
|
||||
ON public.pagos USING btree (metodo_id);
|
||||
|
||||
CREATE INDEX idx_pagos_periodo
|
||||
ON public.pagos USING btree (anio_mes_pagado, fecha_pago);
|
||||
|
||||
ALTER TABLE public.pagos ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,29 @@
|
||||
-- ============================================================================
|
||||
-- public.reservas
|
||||
-- ============================================================================
|
||||
-- Reservas activas y canceladas. La cancelación es soft-delete:
|
||||
-- cancelada=true + cancelada_en. UNIQUE (turno_id, cliente_id) evita
|
||||
-- reservas duplicadas del mismo cliente en el mismo turno (incluso
|
||||
-- aunque haya canceladas; el modelo no permite re-reservar tras
|
||||
-- cancelar).
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- public.turnos, public.usuarios
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.reservas (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
turno_id uuid NOT NULL,
|
||||
cliente_id uuid NOT NULL,
|
||||
reservada_en timestamp with time zone DEFAULT now() NOT NULL,
|
||||
cancelada boolean DEFAULT false NOT NULL,
|
||||
cancelada_en timestamp with time zone,
|
||||
CONSTRAINT reservas_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT reservas_turno_id_cliente_id_key UNIQUE (turno_id, cliente_id),
|
||||
CONSTRAINT reservas_turno_id_fkey
|
||||
FOREIGN KEY (turno_id) REFERENCES public.turnos(id) ON DELETE CASCADE,
|
||||
CONSTRAINT reservas_cliente_id_fkey
|
||||
FOREIGN KEY (cliente_id) REFERENCES public.usuarios(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
ALTER TABLE public.reservas ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,57 @@
|
||||
-- ============================================================================
|
||||
-- public.reservas_huerfanas
|
||||
-- ============================================================================
|
||||
-- Snapshot desconectado: cuando un turno con reservas activas se
|
||||
-- elimina (por desactivación de actividad, cambio de plantilla, cierre
|
||||
-- de día especial), las reservas vivas se rescatan acá. Conservan los
|
||||
-- datos necesarios para reubicar al cliente: actividad_nombre, fecha,
|
||||
-- hora original.
|
||||
--
|
||||
-- estado_resolucion ∈ {pendiente, reubicado, resuelta}.
|
||||
-- pendiente: nadie hizo nada, requiere atención del operador (o del cliente).
|
||||
-- reubicado: el operador la movió a un turno nuevo (fc_mover_reserva_huerfana).
|
||||
-- resuelta: el operador la dio por cerrada sin reubicarla. Típicamente
|
||||
-- después de notificar al cliente y delegarle la re-reserva;
|
||||
-- también cubre "alta por error" u otros cierres manuales.
|
||||
-- El estado es agnóstico al cómo se llegó: el tracking de
|
||||
-- notificación efectiva vive en otro módulo.
|
||||
--
|
||||
-- resuelto_en: timestamp del último cambio a estado terminal. NULL si
|
||||
-- estado_resolucion='pendiente', NOT NULL si reubicado/resuelta
|
||||
-- (invariante reforzada por CHECK). Lo escriben
|
||||
-- fc_mover_reserva_huerfana y fc_resolver_huerfana. La purga
|
||||
-- (internal.limpiar_huerfanas_resueltas) exige que ya pasaron al menos
|
||||
-- `retencion.huerfanas_resueltas_dias` (default 14) desde este
|
||||
-- timestamp para considerar la fila elegible, de modo que el operador
|
||||
-- tenga una ventana de gracia para deshacer un cambio de estado por
|
||||
-- error (volverla a `pendiente` resetea resuelto_en a NULL y le
|
||||
-- devuelve la inmunidad).
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- public.usuarios
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.reservas_huerfanas (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
cliente_id uuid NOT NULL,
|
||||
actividad_nombre text NOT NULL,
|
||||
fecha_original date NOT NULL,
|
||||
hora_inicio_original time without time zone NOT NULL,
|
||||
estado_resolucion character varying(20) DEFAULT 'pendiente'::character varying NOT NULL,
|
||||
creada_en timestamp with time zone DEFAULT now() NOT NULL,
|
||||
resuelto_en timestamp with time zone,
|
||||
CONSTRAINT reservas_huerfanas_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT reservas_huerfanas_estado_check
|
||||
CHECK (((estado_resolucion)::text = ANY (ARRAY[
|
||||
('pendiente'::character varying)::text,
|
||||
('reubicado'::character varying)::text,
|
||||
('resuelta'::character varying)::text
|
||||
]))),
|
||||
CONSTRAINT reservas_huerfanas_resuelto_en_coherente
|
||||
CHECK ((((estado_resolucion)::text = 'pendiente'::text) AND (resuelto_en IS NULL))
|
||||
OR (((estado_resolucion)::text <> 'pendiente'::text) AND (resuelto_en IS NOT NULL))),
|
||||
CONSTRAINT reservas_huerfanas_cliente_id_fkey
|
||||
FOREIGN KEY (cliente_id) REFERENCES public.usuarios(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
ALTER TABLE public.reservas_huerfanas ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,24 @@
|
||||
-- ============================================================================
|
||||
-- public.tipos_cuota
|
||||
-- ============================================================================
|
||||
-- Planes que ofrece el gimnasio. Determinan precio, frecuencia semanal,
|
||||
-- día sugerido de pago, recargo opcional y (vía actividades_tipos_cuota)
|
||||
-- qué actividades incluye.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.tipos_cuota (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
nombre character varying(100),
|
||||
descripcion text,
|
||||
dias_semana smallint NOT NULL,
|
||||
precio numeric(10,2) NOT NULL,
|
||||
parasocios boolean NOT NULL,
|
||||
dia_de_pago smallint DEFAULT 10,
|
||||
recargo numeric(10,2),
|
||||
CONSTRAINT tipos_cuota_pkey PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tipos_cuota_parasocios
|
||||
ON public.tipos_cuota USING btree (parasocios, dias_semana);
|
||||
|
||||
ALTER TABLE public.tipos_cuota ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,33 @@
|
||||
-- ============================================================================
|
||||
-- public.turnos
|
||||
-- ============================================================================
|
||||
-- Instancias reservables generadas JIT (just-in-time) a partir de las
|
||||
-- plantillas horarias. UNIQUE (actividad_id, fecha, hora_inicio) garantiza
|
||||
-- que no se materialicen duplicados. El flag `es_especial` indica si el
|
||||
-- turno proviene de horario_actividad_especial (TRUE) o de horario_actividad
|
||||
-- (FALSE).
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- public.actividades
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.turnos (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
actividad_id integer NOT NULL,
|
||||
fecha date NOT NULL,
|
||||
hora_inicio time without time zone NOT NULL,
|
||||
hora_fin time without time zone NOT NULL,
|
||||
capacidad_maxima smallint NOT NULL,
|
||||
activo boolean DEFAULT true,
|
||||
dia_semana smallint NOT NULL,
|
||||
es_especial boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT turnos_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT turnos_actividad_id_fecha_hora_inicio_key
|
||||
UNIQUE (actividad_id, fecha, hora_inicio),
|
||||
CONSTRAINT turnos_check_horas
|
||||
CHECK ((hora_inicio < hora_fin)),
|
||||
CONSTRAINT turnos_actividad_id_fkey
|
||||
FOREIGN KEY (actividad_id) REFERENCES public.actividades(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
ALTER TABLE public.turnos ENABLE ROW LEVEL SECURITY;
|
||||
@@ -0,0 +1,59 @@
|
||||
-- ============================================================================
|
||||
-- public.usuarios
|
||||
-- ============================================================================
|
||||
-- Tabla central de personas del sistema: clientes, admin y superadmin.
|
||||
-- El rol se valida por constraint contra el set {cliente, admin, superadmin}.
|
||||
-- El sexo se valida contra {Hombre, Mujer, Otro}. La contraseña se guarda
|
||||
-- hasheada (bcrypt vía internal.create_password_hash). password_hash es
|
||||
-- NULL para clientes sin contraseña asignada: no inician sesión en este
|
||||
-- panel (usan el bot de WhatsApp).
|
||||
--
|
||||
-- DEPENDE DE
|
||||
-- public.tipos_cuota (FK opcional a su plan por defecto).
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE public.usuarios (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
dni character varying(20) NOT NULL,
|
||||
nombre character varying(100) NOT NULL,
|
||||
apellido character varying(100),
|
||||
peso numeric(5,2),
|
||||
altura integer,
|
||||
rol text NOT NULL,
|
||||
isactive boolean DEFAULT true,
|
||||
fecha_creacion timestamp with time zone DEFAULT now(),
|
||||
fecha_modificacion timestamp with time zone DEFAULT now(),
|
||||
password_hash text,
|
||||
mail character varying(50),
|
||||
telefono character varying(20),
|
||||
fuerza_max numeric(10,2),
|
||||
sexo character varying(10) DEFAULT 'Hombre'::character varying,
|
||||
tipo_cuota uuid,
|
||||
CONSTRAINT usuarios_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT usuarios_dni_key UNIQUE (dni),
|
||||
CONSTRAINT usuarios_rol_check
|
||||
CHECK ((rol = ANY (ARRAY[
|
||||
('cliente'::character varying)::text,
|
||||
('admin'::character varying)::text,
|
||||
('superadmin'::character varying)::text
|
||||
]))),
|
||||
CONSTRAINT nuevo_nombre_sexo_check
|
||||
CHECK (((sexo)::text = ANY (ARRAY[
|
||||
('Hombre'::character varying)::text,
|
||||
('Mujer'::character varying)::text,
|
||||
('Otro'::character varying)::text
|
||||
]))),
|
||||
CONSTRAINT fk_usuario_tipo_cuota
|
||||
FOREIGN KEY (tipo_cuota) REFERENCES public.tipos_cuota(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_usuarios_activo
|
||||
ON public.usuarios USING btree (isactive, rol);
|
||||
|
||||
CREATE INDEX idx_usuarios_rol
|
||||
ON public.usuarios USING btree (rol) WHERE (isactive = true);
|
||||
|
||||
CREATE INDEX idx_usuarios_tipo_cuota
|
||||
ON public.usuarios USING btree (tipo_cuota) WHERE (tipo_cuota IS NOT NULL);
|
||||
|
||||
ALTER TABLE public.usuarios ENABLE ROW LEVEL SECURITY;
|
||||
Reference in New Issue
Block a user