Agrego carpeta del source sql de la Base de Datos

This commit is contained in:
Pablo
2026-08-22 19:10:49 -03:00
parent 11e36bd6c2
commit 88d724fbcf
94 changed files with 7820 additions and 0 deletions
@@ -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;
+49
View File
@@ -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;
+87
View File
@@ -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;
+33
View File
@@ -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;