Agrego carpeta del source sql de la Base de Datos
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
-- ============================================================================
|
||||
-- fc_cambiar_propia_contrasena
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Permite a un admin o superadmin cambiar su propia contraseña,
|
||||
-- confirmando primero la contraseña actual. Autoservicio: no recibe el id
|
||||
-- de ningún otro usuario, solo opera sobre el dueño del token.
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- p_token UUID sesión del actor (también identifica de quién
|
||||
-- es la contraseña a cambiar).
|
||||
-- p_password_actual TEXT contraseña actual en texto plano, para
|
||||
-- confirmar identidad antes de cambiarla.
|
||||
-- p_password_nueva TEXT contraseña nueva en texto plano (mínimo 8
|
||||
-- caracteres; se valida también en el cliente,
|
||||
-- pero el servidor es la última línea).
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- Requiere permiso 'cambiar_propia_contrasena'.
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- - 'La contraseña actual es incorrecta.'
|
||||
-- - 'La contraseña nueva debe tener al menos 8 caracteres.'
|
||||
--
|
||||
-- RETORNA
|
||||
-- void.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- UPDATE de password_hash en public.usuarios, fila del propio actor.
|
||||
-- fecha_modificacion no se toca (no es un cambio de "perfil").
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.fc_cambiar_propia_contrasena(
|
||||
p_token UUID,
|
||||
p_password_actual TEXT,
|
||||
p_password_nueva TEXT
|
||||
)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path TO extensions, public
|
||||
AS $function$
|
||||
DECLARE
|
||||
v_user_id UUID;
|
||||
v_stored_password_hash TEXT;
|
||||
BEGIN
|
||||
PERFORM internal.validate_permission(p_token := p_token, p_accion := 'cambiar_propia_contrasena');
|
||||
|
||||
IF p_password_nueva IS NULL OR length(p_password_nueva) < 8 THEN
|
||||
RAISE EXCEPTION 'La contraseña nueva debe tener al menos 8 caracteres.';
|
||||
END IF;
|
||||
|
||||
SELECT usuario_id INTO v_user_id FROM internal.sesiones WHERE token = p_token;
|
||||
|
||||
SELECT password_hash INTO v_stored_password_hash
|
||||
FROM public.usuarios
|
||||
WHERE id = v_user_id
|
||||
FOR UPDATE;
|
||||
|
||||
IF v_stored_password_hash IS NULL
|
||||
OR v_stored_password_hash <> crypt(p_password_actual, v_stored_password_hash) THEN
|
||||
RAISE EXCEPTION 'La contraseña actual es incorrecta.';
|
||||
END IF;
|
||||
|
||||
UPDATE public.usuarios
|
||||
SET password_hash = internal.create_password_hash(p_password_nueva)
|
||||
WHERE id = v_user_id;
|
||||
END;
|
||||
$function$;
|
||||
@@ -0,0 +1,59 @@
|
||||
-- ============================================================================
|
||||
-- fc_eliminar_usuario
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Elimina un usuario (DELETE real). Si tiene registros asociados (pagos,
|
||||
-- historial, etc.) y un FK lo impide, atrapa la violación y devuelve un
|
||||
-- mensaje amigable al frontend.
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- p_dni TEXT DNI del usuario a eliminar.
|
||||
-- p_token UUID sesión del actor.
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- Requiere permiso 'eliminar_usuarios'.
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- - 'No se puede eliminar el usuario X porque tiene registros asociados
|
||||
-- (pagos, historial, etc). Primero debe archivar o borrar esos datos.'
|
||||
-- - 'No existe un usuario con el DNI X'
|
||||
--
|
||||
-- RETORNA
|
||||
-- BOOL TRUE en caso de éxito.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- DELETE de la fila en public.usuarios. Las eliminaciones en cascada
|
||||
-- dependen de los FKs definidos en las tablas hijas.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.fc_eliminar_usuario(p_dni text, p_token uuid)
|
||||
RETURNS bool
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path TO public
|
||||
AS $function$
|
||||
DECLARE
|
||||
v_id_eliminado UUID;
|
||||
|
||||
BEGIN
|
||||
|
||||
PERFORM internal.validate_permission(p_token := p_token, p_accion := 'eliminar_usuarios');
|
||||
|
||||
BEGIN
|
||||
DELETE FROM public.usuarios
|
||||
WHERE dni = p_dni
|
||||
RETURNING id INTO v_id_eliminado;
|
||||
|
||||
EXCEPTION
|
||||
WHEN foreign_key_violation THEN
|
||||
-- Este mensaje es mucho más amigable que el error nativo de Postgres
|
||||
RAISE EXCEPTION 'No se puede eliminar el usuario % porque tiene registros asociados (pagos, historial, etc). Primero debe archivar o borrar esos datos.', p_dni;
|
||||
END;
|
||||
|
||||
-- 3. Validar si encontró al usuario
|
||||
IF v_id_eliminado IS NULL THEN
|
||||
RAISE EXCEPTION 'No existe un usuario con el DNI %', p_dni;
|
||||
END IF;
|
||||
RETURN TRUE;
|
||||
END;
|
||||
$function$;
|
||||
@@ -0,0 +1,143 @@
|
||||
-- ============================================================================
|
||||
-- fc_insertar_usuario
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Crea un usuario nuevo. Si el rol es 'admin' o 'superadmin', exige un
|
||||
-- permiso adicional sobre el del actor. Hashea la contraseña recibida en
|
||||
-- texto plano antes de persistir.
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- p_datos JSONB con el payload del usuario. Claves:
|
||||
-- dni TEXT (obligatorio)
|
||||
-- nombre TEXT (obligatorio)
|
||||
-- apellido TEXT (opcional)
|
||||
-- mail TEXT (opcional)
|
||||
-- telefono TEXT (opcional)
|
||||
-- peso DECIMAL (opcional)
|
||||
-- altura INTEGER (opcional)
|
||||
-- fuerza_max DECIMAL (opcional)
|
||||
-- sexo TEXT (opcional, default 'Hombre')
|
||||
-- rol TEXT (obligatorio) p.ej. 'cliente', 'admin', 'superadmin'.
|
||||
-- password TEXT (obligatorio si rol != 'cliente'; opcional si
|
||||
-- rol = 'cliente') en texto plano; se hashea
|
||||
-- internamente.
|
||||
-- isActive BOOLEAN (obligatorio).
|
||||
-- tipo_cuota UUID (opcional) plan asociado. Si se pasa debe existir
|
||||
-- en tipos_cuota.
|
||||
-- p_token UUID sesión del actor.
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- Requiere permiso 'modificar_usuarios'. Si rol = 'superadmin' exige además
|
||||
-- 'agregar_superadmin'. Si rol = 'admin' exige además 'agregar_admin'.
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- - 'El tipo_cuota X no existe.'
|
||||
-- - 'La contraseña es obligatoria' (si rol != 'cliente' y no se mandó)
|
||||
-- - 'Error al generar el hash de la contraseña'
|
||||
--
|
||||
-- RETORNA
|
||||
-- void.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- INSERT en public.usuarios con password_hash calculado. Si rol = 'cliente'
|
||||
-- y no se mandó password, password_hash se inserta NULL (los clientes no
|
||||
-- inician sesión en este panel, usan el bot de WhatsApp).
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.fc_insertar_usuario(p_datos jsonb, p_token uuid)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path TO extensions, public
|
||||
AS $function$
|
||||
DECLARE
|
||||
v_password_hash TEXT;
|
||||
v_rol TEXT;
|
||||
|
||||
-- Campos del nuevo usuario
|
||||
new_dni TEXT;
|
||||
new_nombre TEXT;
|
||||
new_apellido TEXT;
|
||||
new_mail TEXT;
|
||||
new_telefono TEXT;
|
||||
new_peso DECIMAL(5,2);
|
||||
new_altura INTEGER;
|
||||
new_fuerza_max DECIMAL(10,2);
|
||||
new_sexo TEXT;
|
||||
new_rol TEXT;
|
||||
new_password TEXT;
|
||||
new_isactive BOOL;
|
||||
new_tipo_cuota UUID;
|
||||
|
||||
BEGIN
|
||||
-- 1. Validar el token de sesión y obtener el rol del usuario que llama.
|
||||
PERFORM internal.validate_permission(p_token := p_token, p_accion := 'modificar_usuarios');
|
||||
|
||||
-- 2. Validar que no esté intentando crear un admin o superadmin si no puede
|
||||
|
||||
new_rol := p_datos->>'rol';
|
||||
|
||||
IF new_rol = 'superadmin' THEN
|
||||
PERFORM internal.validate_permission(p_token := p_token, p_accion := 'agregar_superadmin');
|
||||
ELSIF new_rol = 'admin' THEN
|
||||
PERFORM internal.validate_permission(p_token := p_token, p_accion := 'agregar_admin');
|
||||
END IF;
|
||||
|
||||
-- 2.5 Extraer campos del JSONB
|
||||
new_dni := p_datos->>'dni';
|
||||
new_nombre := p_datos->>'nombre';
|
||||
new_apellido := p_datos->>'apellido';
|
||||
new_mail := p_datos->>'mail';
|
||||
new_telefono := p_datos->>'telefono';
|
||||
new_peso := (p_datos->>'peso')::DECIMAL;
|
||||
new_altura := (p_datos->>'altura')::INTEGER;
|
||||
new_fuerza_max := (p_datos->>'fuerza_max')::DECIMAL;
|
||||
new_sexo := COALESCE(p_datos->>'sexo', 'Hombre');
|
||||
|
||||
new_password := p_datos->>'password';
|
||||
new_isactive := (p_datos->>'isActive')::BOOLEAN;
|
||||
|
||||
-- CORRECCIÓN 1: Validar que exista la key Y que NO sea null antes de buscar en la BD
|
||||
IF p_datos ? 'tipo_cuota' AND (p_datos->>'tipo_cuota') IS NOT NULL THEN
|
||||
new_tipo_cuota := (p_datos->>'tipo_cuota')::UUID;
|
||||
|
||||
-- Validar que exista el tipo_cuota en la tabla padre
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM tipos_cuota
|
||||
WHERE id = new_tipo_cuota
|
||||
) THEN
|
||||
RAISE EXCEPTION 'El tipo_cuota % no existe.', new_tipo_cuota;
|
||||
END IF;
|
||||
ELSE
|
||||
-- Si no viene o es null, asignamos NULL explícitamente
|
||||
new_tipo_cuota := NULL;
|
||||
END IF;
|
||||
|
||||
-- La contraseña es obligatoria salvo para clientes: no necesitan loguearse
|
||||
-- en este panel (usan el bot de WhatsApp). Si es cliente y no la mandan,
|
||||
-- password_hash queda NULL.
|
||||
IF new_password IS NULL AND new_rol <> 'cliente' THEN
|
||||
RAISE EXCEPTION 'La contraseña es obligatoria';
|
||||
END IF;
|
||||
|
||||
-- 3. Hashear la contraseña solo si vino (clientes pueden no traerla)
|
||||
IF new_password IS NOT NULL THEN
|
||||
SELECT internal.create_password_hash(new_password) INTO v_password_hash;
|
||||
IF v_password_hash IS NULL THEN
|
||||
RAISE EXCEPTION 'Error al generar el hash de la contraseña';
|
||||
END IF;
|
||||
ELSE
|
||||
v_password_hash := NULL;
|
||||
END IF;
|
||||
|
||||
-- 4. Insertar el nuevo usuario
|
||||
-- CORRECCIÓN 2: Agregué 'tipo_cuota' y 'new_tipo_cuota' al INSERT
|
||||
INSERT INTO public.usuarios (
|
||||
dni, nombre, apellido, mail, telefono, peso, altura, fuerza_max,
|
||||
sexo, rol, password_hash, isactive, tipo_cuota
|
||||
) VALUES (
|
||||
new_dni, new_nombre, new_apellido, new_mail, new_telefono, new_peso, new_altura, new_fuerza_max,
|
||||
new_sexo, new_rol, v_password_hash, new_isactive, new_tipo_cuota
|
||||
);
|
||||
END;
|
||||
$function$;
|
||||
@@ -0,0 +1,41 @@
|
||||
-- ============================================================================
|
||||
-- fc_modificar_estado_usuario
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Activa o desactiva un usuario (toggle de isactive). Pensado para "dar
|
||||
-- de baja" sin perder el registro.
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- p_token UUID sesión del actor.
|
||||
-- p_id UUID ID del usuario.
|
||||
-- p_estado BOOLEAN nuevo valor de isactive.
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- Requiere permiso 'desactivar_usuarios'. (El mismo permiso cubre
|
||||
-- activación y desactivación.)
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- Ninguno propio. Si p_id no existe, el UPDATE no afecta filas y la
|
||||
-- función igualmente termina sin error.
|
||||
--
|
||||
-- RETORNA
|
||||
-- void.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- UPDATE de isactive en public.usuarios. NO toca fecha_modificacion.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.fc_modificar_estado_usuario(p_token uuid, p_id uuid, p_estado boolean)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path TO public
|
||||
AS $function$
|
||||
BEGIN
|
||||
PERFORM internal.validate_permission(p_token := p_token, p_accion := 'desactivar_usuarios');
|
||||
|
||||
update public.usuarios
|
||||
set isactive = p_estado
|
||||
where id = p_id;
|
||||
END;
|
||||
$function$;
|
||||
@@ -0,0 +1,44 @@
|
||||
-- ============================================================================
|
||||
-- fc_modificar_rol_usuario
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Cambia el rol de un usuario y actualiza su fecha_modificacion.
|
||||
-- NO valida que p_nuevo_rol sea un valor permitido; el constraint de
|
||||
-- tabla es la última línea de defensa.
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- p_uid UUID ID del usuario a modificar.
|
||||
-- p_nuevo_rol TEXT nuevo valor de rol.
|
||||
-- p_token UUID sesión del actor.
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- Requiere permiso 'modificar_rol_usuarios'.
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- Ninguno propio (sólo los del validador de permiso o constraint de tabla).
|
||||
-- Si p_uid no existe, no hace nada (no rebota).
|
||||
--
|
||||
-- RETORNA
|
||||
-- void.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- UPDATE en public.usuarios: setea rol y fecha_modificacion = now().
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.fc_modificar_rol_usuario(p_uid uuid, p_nuevo_rol text, p_token uuid)
|
||||
RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path TO public
|
||||
AS $function$
|
||||
DECLARE
|
||||
BEGIN
|
||||
-- Exigir el permiso específico para esta acción
|
||||
PERFORM internal.validate_permission(p_token := p_token, p_accion := 'modificar_rol_usuarios');
|
||||
|
||||
UPDATE public.usuarios
|
||||
SET rol = p_nuevo_rol,
|
||||
fecha_modificacion = now()
|
||||
WHERE id = p_uid;
|
||||
END;
|
||||
$function$;
|
||||
@@ -0,0 +1,141 @@
|
||||
-- ============================================================================
|
||||
-- fc_modificar_usuario
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Modifica los datos de un usuario existente. Semántica PATCH: sólo se
|
||||
-- actualizan las claves presentes en p_datos. Identifica al usuario por
|
||||
-- id; si no viene, usa dni como fallback.
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- p_datos JSONB con los campos a actualizar. Para identificar al usuario:
|
||||
-- id UUID (preferido)
|
||||
-- dni TEXT (fallback si no se pasa id)
|
||||
-- Campos editables (todos opcionales):
|
||||
-- nombre, apellido, peso, altura, mail, telefono, fuerza_max, sexo,
|
||||
-- tipo_cuota, rol, password.
|
||||
-- Caso especial - rol:
|
||||
-- Si se pasa 'rol' y es distinto al actual, se delega a
|
||||
-- fc_modificar_rol_usuario (que valida un permiso adicional). Tras la
|
||||
-- delegación, la key 'rol' se elimina del UPDATE principal para evitar
|
||||
-- escribir dos veces el mismo campo.
|
||||
-- Caso especial - tipo_cuota:
|
||||
-- Si se pasa la key con valor NULL, el plan se desasocia (queda NULL).
|
||||
-- Si no se pasa la key, no se toca.
|
||||
-- Caso especial - password:
|
||||
-- Opcional; solo superadmin puede setearla (permiso dedicado). Si se
|
||||
-- omite o viene vacía, no se toca la contraseña existente.
|
||||
-- p_token UUID sesión del actor.
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- Requiere permiso 'modificar_usuarios'. Si se cambia el rol, además
|
||||
-- requiere 'modificar_rol_usuarios' (impuesto por la función delegada).
|
||||
-- Si se pasa 'password' no vacía, además requiere
|
||||
-- 'resetear_contrasena_usuario'.
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- - 'Se requiere un ID o un DNI para identificar al usuario.'
|
||||
-- - 'Usuario no encontrado con los datos proporcionados.'
|
||||
-- - 'El tipo_cuota X no existe.'
|
||||
-- - 'Error al generar el hash de la contraseña'
|
||||
--
|
||||
-- RETORNA
|
||||
-- void.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- - UPDATE en public.usuarios con fecha_modificacion = now().
|
||||
-- - Si hubo cambio de rol, además UPDATE adicional vía
|
||||
-- fc_modificar_rol_usuario.
|
||||
-- - Si vino 'password' no vacía, además actualiza password_hash.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.fc_modificar_usuario(p_datos jsonb, p_token uuid)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path TO extensions, public
|
||||
AS $function$
|
||||
DECLARE
|
||||
v_isActive_new BOOLEAN := NULL;
|
||||
v_rol_actual TEXT;
|
||||
v_uid UUID;
|
||||
v_password_hash TEXT;
|
||||
BEGIN
|
||||
|
||||
-- Validar token
|
||||
PERFORM internal.validate_permission(p_token := p_token, p_accion := 'modificar_usuarios');
|
||||
|
||||
-- Evaluamos primero si el JSON trae un ID válido
|
||||
IF p_datos ? 'id' AND p_datos->>'id' IS NOT NULL THEN
|
||||
SELECT u.id, u.rol INTO v_uid, v_rol_actual
|
||||
FROM public.usuarios u
|
||||
WHERE u.id = (p_datos->>'id')::UUID
|
||||
FOR UPDATE;
|
||||
|
||||
-- Si no hay ID, intentamos usar el DNI (fallback)
|
||||
ELSIF p_datos ? 'dni' AND p_datos->>'dni' IS NOT NULL THEN
|
||||
SELECT u.id, u.rol INTO v_uid, v_rol_actual
|
||||
FROM public.usuarios u
|
||||
WHERE u.dni = p_datos->>'dni'
|
||||
FOR UPDATE;
|
||||
|
||||
-- Si no mandaron ni ID ni DNI, no sabemos a quién modificar
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Se requiere un ID o un DNI para identificar al usuario.';
|
||||
END IF;
|
||||
|
||||
-- Si hicimos la búsqueda pero no encontramos coincidencias
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'Usuario no encontrado con los datos proporcionados.';
|
||||
END IF;
|
||||
|
||||
-- Validar existencia del tipo_cuota SOLO si viene en el JSON y NO es null
|
||||
IF p_datos ? 'tipo_cuota' AND (p_datos->>'tipo_cuota') IS NOT NULL THEN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM tipos_cuota
|
||||
WHERE id = (p_datos->>'tipo_cuota')::UUID
|
||||
) THEN
|
||||
RAISE EXCEPTION 'El tipo_cuota % no existe.', p_datos->>'tipo_cuota';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Validar si viene un rol en el JSON y si es distinto al que ya está en la DB
|
||||
IF p_datos ? 'rol' AND (p_datos->>'rol') IS DISTINCT FROM v_rol_actual THEN
|
||||
-- Delegamos la responsabilidad a la nueva función
|
||||
PERFORM public.fc_modificar_rol_usuario(v_uid, p_datos->>'rol', p_token);
|
||||
|
||||
-- Si llega hasta acá (no tiró excepción), eliminamos la key 'rol' del json
|
||||
-- para que el UPDATE de abajo no lo sobreescriba innecesariamente.
|
||||
p_datos := p_datos - 'rol';
|
||||
END IF;
|
||||
|
||||
-- Password opcional: solo si viene la key Y no es null/vacía, se re-hashea
|
||||
-- y se agrega al UPDATE. Acción sensible -> permiso dedicado, no alcanza
|
||||
-- con 'modificar_usuarios' (mismo criterio que ya se usa para 'rol').
|
||||
IF p_datos ? 'password' AND COALESCE(p_datos->>'password', '') <> '' THEN
|
||||
PERFORM internal.validate_permission(p_token := p_token, p_accion := 'resetear_contrasena_usuario');
|
||||
v_password_hash := internal.create_password_hash(p_datos->>'password');
|
||||
IF v_password_hash IS NULL THEN
|
||||
RAISE EXCEPTION 'Error al generar el hash de la contraseña';
|
||||
END IF;
|
||||
ELSE
|
||||
v_password_hash := NULL;
|
||||
END IF;
|
||||
|
||||
UPDATE public.usuarios
|
||||
SET
|
||||
nombre = CASE WHEN p_datos ? 'nombre' THEN p_datos->>'nombre' ELSE nombre END,
|
||||
apellido = CASE WHEN p_datos ? 'apellido' THEN p_datos->>'apellido' ELSE apellido END,
|
||||
peso = CASE WHEN p_datos ? 'peso' THEN (p_datos->>'peso')::DECIMAL ELSE peso END,
|
||||
altura = CASE WHEN p_datos ? 'altura' THEN (p_datos->>'altura')::DECIMAL ELSE altura END,
|
||||
mail = CASE WHEN p_datos ? 'mail' THEN p_datos->>'mail' ELSE mail END,
|
||||
telefono = CASE WHEN p_datos ? 'telefono' THEN p_datos->>'telefono' ELSE telefono END,
|
||||
fuerza_max= CASE WHEN p_datos ? 'fuerza_max' THEN (p_datos->>'fuerza_max')::DECIMAL ELSE fuerza_max END,
|
||||
sexo = CASE WHEN p_datos ? 'sexo' THEN p_datos->>'sexo' ELSE sexo END,
|
||||
-- Esto ahora permite cambiar el tipo de cuota a NULL si mandas null, o cambiarlo a otro ID
|
||||
tipo_cuota= CASE WHEN p_datos ? 'tipo_cuota' THEN (p_datos->>'tipo_cuota')::UUID ELSE tipo_cuota END,
|
||||
password_hash = CASE WHEN v_password_hash IS NOT NULL THEN v_password_hash ELSE password_hash END,
|
||||
fecha_modificacion = now()
|
||||
WHERE id = v_uid;
|
||||
|
||||
END;
|
||||
$function$;
|
||||
@@ -0,0 +1,65 @@
|
||||
-- ============================================================================
|
||||
-- fc_obtener_usuario_por_token
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Devuelve los datos del usuario asociado a una sesión vigente identificada
|
||||
-- por p_user_token. El segundo parámetro (p_token) es la sesión del actor
|
||||
-- que realiza la consulta y se usa sólo para autorizar.
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- p_token UUID sesión del actor que llama (autorización).
|
||||
-- p_user_token UUID token de la sesión del usuario que se quiere consultar.
|
||||
-- La sesión debe estar vigente (expires_at > NOW()).
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- Requiere permiso 'ver_usuarios'.
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- Ninguno propio (sólo los del validador de permiso).
|
||||
--
|
||||
-- RETORNA
|
||||
-- JSONB objeto único con los datos del usuario (id, dni, nombre, apellido,
|
||||
-- mail, telefono, peso, altura, sexo, fuerza_max, isActive, rol,
|
||||
-- tipo_cuota, fecha_creacion, fecha_modificacion). Si p_user_token no
|
||||
-- corresponde a una sesión vigente, retorna NULL.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- Ninguno (STABLE).
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.fc_obtener_usuario_por_token(p_token UUID, p_user_token UUID)
|
||||
RETURNS JSONB
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
STABLE
|
||||
AS $$
|
||||
DECLARE
|
||||
BEGIN
|
||||
|
||||
PERFORM internal.validate_permission(p_token, 'ver_usuarios');
|
||||
|
||||
RETURN (
|
||||
SELECT jsonb_build_object(
|
||||
'id', u.id,
|
||||
'dni', u.dni,
|
||||
'nombre', u.nombre,
|
||||
'mail', u.mail,
|
||||
'peso', u.peso,
|
||||
'sexo', u.sexo,
|
||||
'altura', u.altura,
|
||||
'apellido', u.apellido,
|
||||
'fecha_creacion', u.fecha_creacion,
|
||||
'fecha_modificacion', u.fecha_modificacion,
|
||||
'fuerza_max', u.fuerza_max,
|
||||
'isActive', u.isactive,
|
||||
'rol', u.rol,
|
||||
'telefono', u.telefono,
|
||||
'tipo_cuota', u.tipo_cuota
|
||||
)
|
||||
FROM public.usuarios u
|
||||
JOIN internal.sesiones s ON s.usuario_id = u.id
|
||||
AND s.token = p_user_token
|
||||
AND s.expires_at > NOW()
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
@@ -0,0 +1,79 @@
|
||||
-- ============================================================================
|
||||
-- fc_obtener_usuarios
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Devuelve un lote paginado de usuarios. Opcionalmente filtra por DNI.
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- p_token UUID sesión del actor.
|
||||
-- p_dni TEXT (opcional, default NULL) si se pasa, sólo retorna el
|
||||
-- usuario con ese DNI. NULL = todos.
|
||||
-- p_pagina INT (default 1) página, base 1.
|
||||
-- p_cantidad INT (default 50) ítems por página.
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- Requiere permiso 'ver_usuarios'.
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- Ninguno propio (sólo los del validador de permiso).
|
||||
--
|
||||
-- RETORNA
|
||||
-- JSONB array (puede ser vacío). Cada ítem: id, dni, nombre, apellido,
|
||||
-- peso, altura, fecha_creacion, fecha_modificacion, telefono, fuerza_max,
|
||||
-- sexo, tipo_cuota, mail, rol, isActive. Ordenado por nombre, apellido.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- Ninguno (STABLE).
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION fc_obtener_usuarios(
|
||||
p_token UUID,
|
||||
p_dni TEXT DEFAULT NULL,
|
||||
p_pagina INT DEFAULT 1, -- Página actual (empieza en 1)
|
||||
p_cantidad INT DEFAULT 50 -- Cuántos traer (default 30)
|
||||
)
|
||||
RETURNS JSONB
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
STABLE
|
||||
AS $$
|
||||
DECLARE
|
||||
v_offset INT;
|
||||
BEGIN
|
||||
-- 1. Validar permisos
|
||||
PERFORM internal.validate_permission(p_token, 'ver_usuarios');
|
||||
|
||||
-- 2. Calcular desde dónde empezar a leer (OFFSET)
|
||||
-- Si página es 1, offset es 0. Si es 2, offset es 20, etc.
|
||||
v_offset := (p_pagina - 1) * p_cantidad;
|
||||
|
||||
-- 3. Consulta corregida: Paginación dentro, JSON fuera
|
||||
RETURN (
|
||||
SELECT COALESCE(jsonb_agg(t.usuario_json), '[]'::jsonb)
|
||||
FROM (
|
||||
SELECT jsonb_build_object(
|
||||
'id', u.id,
|
||||
'dni', u.dni,
|
||||
'nombre', u.nombre,
|
||||
'apellido', u.apellido,
|
||||
'peso', u.peso,
|
||||
'altura', u.altura,
|
||||
'fecha_creacion', u.fecha_creacion,
|
||||
'fecha_modificacion', u.fecha_modificacion,
|
||||
'telefono', u.telefono,
|
||||
'fuerza_max', u.fuerza_max,
|
||||
'sexo', u.sexo,
|
||||
'tipo_cuota', u.tipo_cuota,
|
||||
'mail', u.mail,
|
||||
'rol', u.rol,
|
||||
'isActive', u.isactive
|
||||
) AS usuario_json
|
||||
FROM public.usuarios u
|
||||
WHERE (p_dni IS NULL OR u.dni = p_dni)
|
||||
ORDER BY u.nombre, u.apellido -- Vital para que el OFFSET sea consistente
|
||||
LIMIT p_cantidad
|
||||
OFFSET v_offset
|
||||
) t -- 't' es la subconsulta con solo 50 filas
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
@@ -0,0 +1,66 @@
|
||||
-- ============================================================================
|
||||
-- fc_resetear_contrasena_usuario
|
||||
-- ============================================================================
|
||||
-- PROPÓSITO
|
||||
-- Permite al superadmin fijar una contraseña nueva para cualquier usuario
|
||||
-- no-cliente (admin o superadmin), sin pedir ni validar la contraseña
|
||||
-- actual del usuario objetivo. Reset administrativo sin restricción.
|
||||
--
|
||||
-- PARÁMETROS
|
||||
-- p_token UUID sesión del actor (debe ser superadmin).
|
||||
-- p_usuario_id UUID id del usuario objetivo (admin o superadmin).
|
||||
-- p_password_nueva TEXT contraseña nueva en texto plano (mínimo 8
|
||||
-- caracteres).
|
||||
--
|
||||
-- AUTORIZACIÓN
|
||||
-- Requiere permiso 'resetear_contrasena_usuario' (solo superadmin).
|
||||
--
|
||||
-- ERRORES (RAISE EXCEPTION)
|
||||
-- - 'La contraseña nueva debe tener al menos 8 caracteres.'
|
||||
-- - 'Usuario no encontrado.'
|
||||
-- - 'No se puede resetear la contraseña de un cliente: no inician sesión en este panel.'
|
||||
--
|
||||
-- RETORNA
|
||||
-- void.
|
||||
--
|
||||
-- EFECTOS SECUNDARIOS
|
||||
-- UPDATE de password_hash en public.usuarios, fila de p_usuario_id.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.fc_resetear_contrasena_usuario(
|
||||
p_token UUID,
|
||||
p_usuario_id UUID,
|
||||
p_password_nueva TEXT
|
||||
)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path TO extensions, public
|
||||
AS $function$
|
||||
DECLARE
|
||||
v_rol_objetivo TEXT;
|
||||
BEGIN
|
||||
PERFORM internal.validate_permission(p_token := p_token, p_accion := 'resetear_contrasena_usuario');
|
||||
|
||||
IF p_password_nueva IS NULL OR length(p_password_nueva) < 8 THEN
|
||||
RAISE EXCEPTION 'La contraseña nueva debe tener al menos 8 caracteres.';
|
||||
END IF;
|
||||
|
||||
SELECT rol INTO v_rol_objetivo
|
||||
FROM public.usuarios
|
||||
WHERE id = p_usuario_id
|
||||
FOR UPDATE;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'Usuario no encontrado.';
|
||||
END IF;
|
||||
|
||||
IF v_rol_objetivo = 'cliente' THEN
|
||||
RAISE EXCEPTION 'No se puede resetear la contraseña de un cliente: no inician sesión en este panel.';
|
||||
END IF;
|
||||
|
||||
UPDATE public.usuarios
|
||||
SET password_hash = internal.create_password_hash(p_password_nueva)
|
||||
WHERE id = p_usuario_id;
|
||||
END;
|
||||
$function$;
|
||||
Reference in New Issue
Block a user