Sistema NEXUS v3.4 migrado con: Estructura principal: - core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles) - core/catalog: Catalogo de funcionalidades reutilizables - shared/knowledge-base: Base de conocimiento compartida - devtools/scripts: Herramientas de desarrollo - control-plane/registries: Control de servicios y CI/CD - orchestration/: Configuracion de orquestacion de agentes Proyectos incluidos (11): - gamilit (submodule -> GitHub) - trading-platform (OrbiquanTIA) - erp-suite con 5 verticales: - erp-core, construccion, vidrio-templado - mecanicas-diesel, retail, clinicas - betting-analytics - inmobiliaria-analytics - platform_marketing_content - pos-micro, erp-basico Configuracion: - .gitignore completo para Node.js/Python/Docker - gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git) - Sistema de puertos estandarizado (3005-3199) Generated with NEXUS v3.4 Migration System EPIC-010: Configuracion Git y Repositorios
531 lines
20 KiB
PL/PgSQL
531 lines
20 KiB
PL/PgSQL
-- ============================================================================
|
|
-- ET-PROJ-002: Row-Level Security (RLS) Policies
|
|
-- Tablas: projects.stages, projects.blocks, projects.lots, projects.housing_units
|
|
-- Fecha: 2025-11-17
|
|
-- Descripción: Políticas de seguridad para estructura jerárquica multi-tenant
|
|
-- ============================================================================
|
|
|
|
-- Nota: Estas tablas heredan constructora_id de la tabla projects.projects
|
|
-- RLS filtra basándose en constructora_id para aislamiento de datos.
|
|
|
|
-- ============================================================================
|
|
-- HABILITAR RLS EN TODAS LAS TABLAS
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE projects.stages ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE projects.blocks ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE projects.lots ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE projects.housing_units ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- ============================================================================
|
|
-- POLÍTICAS RLS: STAGES (Etapas)
|
|
-- ============================================================================
|
|
|
|
-- SELECT: Ver solo etapas de proyectos de la constructora actual
|
|
DROP POLICY IF EXISTS "stages_select_own_constructora" ON projects.stages;
|
|
|
|
CREATE POLICY "stages_select_own_constructora"
|
|
ON projects.stages
|
|
FOR SELECT
|
|
TO authenticated
|
|
USING (
|
|
constructora_id = public.get_current_constructora_id()
|
|
);
|
|
|
|
COMMENT ON POLICY "stages_select_own_constructora" ON projects.stages IS
|
|
'Permite ver solo etapas de proyectos de la constructora actual.
|
|
Aislamiento: tenant (constructora) level.';
|
|
|
|
-- INSERT: Crear etapas solo para proyectos propios
|
|
DROP POLICY IF EXISTS "stages_insert_own_constructora" ON projects.stages;
|
|
|
|
CREATE POLICY "stages_insert_own_constructora"
|
|
ON projects.stages
|
|
FOR INSERT
|
|
TO authenticated
|
|
WITH CHECK (
|
|
constructora_id = public.get_current_constructora_id()
|
|
AND public.get_current_user_role() IN ('director', 'admin', 'engineer')
|
|
);
|
|
|
|
COMMENT ON POLICY "stages_insert_own_constructora" ON projects.stages IS
|
|
'Permite crear etapas solo para proyectos de la constructora actual.
|
|
Roles permitidos: director, admin, engineer.';
|
|
|
|
-- UPDATE: Actualizar etapas propias
|
|
DROP POLICY IF EXISTS "stages_update_own_constructora" ON projects.stages;
|
|
|
|
CREATE POLICY "stages_update_own_constructora"
|
|
ON projects.stages
|
|
FOR UPDATE
|
|
TO authenticated
|
|
USING (
|
|
constructora_id = public.get_current_constructora_id()
|
|
AND public.get_current_user_role() IN ('director', 'admin', 'engineer', 'resident')
|
|
)
|
|
WITH CHECK (
|
|
constructora_id = public.get_current_constructora_id()
|
|
);
|
|
|
|
COMMENT ON POLICY "stages_update_own_constructora" ON projects.stages IS
|
|
'Permite actualizar etapas propias. Previene cambio de constructora_id.';
|
|
|
|
-- DELETE: Eliminar etapas propias (solo admin/director)
|
|
DROP POLICY IF EXISTS "stages_delete_own_constructora" ON projects.stages;
|
|
|
|
CREATE POLICY "stages_delete_own_constructora"
|
|
ON projects.stages
|
|
FOR DELETE
|
|
TO authenticated
|
|
USING (
|
|
constructora_id = public.get_current_constructora_id()
|
|
AND public.get_current_user_role() IN ('director', 'admin')
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- POLÍTICAS RLS: BLOCKS (Manzanas)
|
|
-- ============================================================================
|
|
|
|
-- SELECT: Ver solo manzanas de proyectos propios
|
|
DROP POLICY IF EXISTS "blocks_select_own_constructora" ON projects.blocks;
|
|
|
|
CREATE POLICY "blocks_select_own_constructora"
|
|
ON projects.blocks
|
|
FOR SELECT
|
|
TO authenticated
|
|
USING (
|
|
constructora_id = public.get_current_constructora_id()
|
|
);
|
|
|
|
COMMENT ON POLICY "blocks_select_own_constructora" ON projects.blocks IS
|
|
'Permite ver solo manzanas de la constructora actual.';
|
|
|
|
-- INSERT: Crear manzanas solo para proyectos propios
|
|
DROP POLICY IF EXISTS "blocks_insert_own_constructora" ON projects.blocks;
|
|
|
|
CREATE POLICY "blocks_insert_own_constructora"
|
|
ON projects.blocks
|
|
FOR INSERT
|
|
TO authenticated
|
|
WITH CHECK (
|
|
constructora_id = public.get_current_constructora_id()
|
|
AND public.get_current_user_role() IN ('director', 'admin', 'engineer')
|
|
);
|
|
|
|
-- UPDATE: Actualizar manzanas propias
|
|
DROP POLICY IF EXISTS "blocks_update_own_constructora" ON projects.blocks;
|
|
|
|
CREATE POLICY "blocks_update_own_constructora"
|
|
ON projects.blocks
|
|
FOR UPDATE
|
|
TO authenticated
|
|
USING (
|
|
constructora_id = public.get_current_constructora_id()
|
|
AND public.get_current_user_role() IN ('director', 'admin', 'engineer', 'resident')
|
|
)
|
|
WITH CHECK (
|
|
constructora_id = public.get_current_constructora_id()
|
|
);
|
|
|
|
-- DELETE: Eliminar manzanas (solo admin/director)
|
|
DROP POLICY IF EXISTS "blocks_delete_own_constructora" ON projects.blocks;
|
|
|
|
CREATE POLICY "blocks_delete_own_constructora"
|
|
ON projects.blocks
|
|
FOR DELETE
|
|
TO authenticated
|
|
USING (
|
|
constructora_id = public.get_current_constructora_id()
|
|
AND public.get_current_user_role() IN ('director', 'admin')
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- POLÍTICAS RLS: LOTS (Lotes)
|
|
-- ============================================================================
|
|
|
|
-- SELECT: Ver solo lotes de proyectos propios
|
|
DROP POLICY IF EXISTS "lots_select_own_constructora" ON projects.lots;
|
|
|
|
CREATE POLICY "lots_select_own_constructora"
|
|
ON projects.lots
|
|
FOR SELECT
|
|
TO authenticated
|
|
USING (
|
|
constructora_id = public.get_current_constructora_id()
|
|
);
|
|
|
|
COMMENT ON POLICY "lots_select_own_constructora" ON projects.lots IS
|
|
'Permite ver solo lotes de la constructora actual.
|
|
Usado en: TreeView, asignación de prototipos, reporte de inventario.';
|
|
|
|
-- INSERT: Crear lotes (incluyendo bulk create)
|
|
DROP POLICY IF EXISTS "lots_insert_own_constructora" ON projects.lots;
|
|
|
|
CREATE POLICY "lots_insert_own_constructora"
|
|
ON projects.lots
|
|
FOR INSERT
|
|
TO authenticated
|
|
WITH CHECK (
|
|
constructora_id = public.get_current_constructora_id()
|
|
AND public.get_current_user_role() IN ('director', 'admin', 'engineer')
|
|
);
|
|
|
|
COMMENT ON POLICY "lots_insert_own_constructora" ON projects.lots IS
|
|
'Permite creación de lotes (individual o masiva hasta 500).
|
|
Validación de constructora_id automática.';
|
|
|
|
-- UPDATE: Actualizar lotes (estado, prototipo, etc.)
|
|
DROP POLICY IF EXISTS "lots_update_own_constructora" ON projects.lots;
|
|
|
|
CREATE POLICY "lots_update_own_constructora"
|
|
ON projects.lots
|
|
FOR UPDATE
|
|
TO authenticated
|
|
USING (
|
|
constructora_id = public.get_current_constructora_id()
|
|
AND (
|
|
-- Director/Admin/Engineer: Full access
|
|
public.get_current_user_role() IN ('director', 'admin', 'engineer')
|
|
OR
|
|
-- Resident: Solo cambiar estado
|
|
public.get_current_user_role() = 'resident'
|
|
)
|
|
)
|
|
WITH CHECK (
|
|
constructora_id = public.get_current_constructora_id()
|
|
);
|
|
|
|
COMMENT ON POLICY "lots_update_own_constructora" ON projects.lots IS
|
|
'Permisos diferenciados por rol:
|
|
- Director/Admin/Engineer: Pueden editar todos los campos
|
|
- Resident: Solo puede cambiar estado (validado en app layer)';
|
|
|
|
-- DELETE: Eliminar lotes (solo si no tiene vivienda)
|
|
DROP POLICY IF EXISTS "lots_delete_own_constructora" ON projects.lots;
|
|
|
|
CREATE POLICY "lots_delete_own_constructora"
|
|
ON projects.lots
|
|
FOR DELETE
|
|
TO authenticated
|
|
USING (
|
|
constructora_id = public.get_current_constructora_id()
|
|
AND public.get_current_user_role() IN ('director', 'admin')
|
|
-- Validación adicional: No eliminar si tiene vivienda (manejado por FK constraint)
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- POLÍTICAS RLS: HOUSING_UNITS (Viviendas)
|
|
-- ============================================================================
|
|
|
|
-- SELECT: Ver solo viviendas de proyectos propios
|
|
DROP POLICY IF EXISTS "housing_units_select_own_constructora" ON projects.housing_units;
|
|
|
|
CREATE POLICY "housing_units_select_own_constructora"
|
|
ON projects.housing_units
|
|
FOR SELECT
|
|
TO authenticated
|
|
USING (
|
|
constructora_id = public.get_current_constructora_id()
|
|
);
|
|
|
|
COMMENT ON POLICY "housing_units_select_own_constructora" ON projects.housing_units IS
|
|
'Permite ver solo viviendas de la constructora actual.
|
|
Usado en: Control de avances, CRM (asignación a derechohabientes).';
|
|
|
|
-- INSERT: Crear viviendas
|
|
DROP POLICY IF EXISTS "housing_units_insert_own_constructora" ON projects.housing_units;
|
|
|
|
CREATE POLICY "housing_units_insert_own_constructora"
|
|
ON projects.housing_units
|
|
FOR INSERT
|
|
TO authenticated
|
|
WITH CHECK (
|
|
constructora_id = public.get_current_constructora_id()
|
|
AND public.get_current_user_role() IN ('director', 'admin', 'engineer')
|
|
);
|
|
|
|
COMMENT ON POLICY "housing_units_insert_own_constructora" ON projects.housing_units IS
|
|
'Permite crear viviendas solo para lotes de la constructora actual.
|
|
Hereda prototipo al momento de creación (snapshot).';
|
|
|
|
-- UPDATE: Actualizar viviendas (avances, estado, etc.)
|
|
DROP POLICY IF EXISTS "housing_units_update_own_constructora" ON projects.housing_units;
|
|
|
|
CREATE POLICY "housing_units_update_own_constructora"
|
|
ON projects.housing_units
|
|
FOR UPDATE
|
|
TO authenticated
|
|
USING (
|
|
constructora_id = public.get_current_constructora_id()
|
|
AND (
|
|
-- Director/Admin/Engineer: Full access
|
|
public.get_current_user_role() IN ('director', 'admin', 'engineer')
|
|
OR
|
|
-- Resident: Actualizar avances físicos
|
|
public.get_current_user_role() = 'resident'
|
|
)
|
|
)
|
|
WITH CHECK (
|
|
constructora_id = public.get_current_constructora_id()
|
|
);
|
|
|
|
COMMENT ON POLICY "housing_units_update_own_constructora" ON projects.housing_units IS
|
|
'Permisos por rol:
|
|
- Director/Admin/Engineer: Editar todo
|
|
- Resident: Actualizar campos de avance (progressCimentacion, progressEstructura, etc.)';
|
|
|
|
-- DELETE: Eliminar viviendas (solo admin/director, validar dependencias)
|
|
DROP POLICY IF EXISTS "housing_units_delete_own_constructora" ON projects.housing_units;
|
|
|
|
CREATE POLICY "housing_units_delete_own_constructora"
|
|
ON projects.housing_units
|
|
FOR DELETE
|
|
TO authenticated
|
|
USING (
|
|
constructora_id = public.get_current_constructora_id()
|
|
AND public.get_current_user_role() IN ('director', 'admin')
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- POLÍTICAS SUPER ADMIN (Bypass para soporte)
|
|
-- ============================================================================
|
|
|
|
-- Super Admin puede ver/editar toda la estructura
|
|
DROP POLICY IF EXISTS "stages_super_admin_all" ON projects.stages;
|
|
CREATE POLICY "stages_super_admin_all" ON projects.stages FOR ALL TO authenticated
|
|
USING (public.get_current_user_role() = 'super_admin')
|
|
WITH CHECK (public.get_current_user_role() = 'super_admin');
|
|
|
|
DROP POLICY IF EXISTS "blocks_super_admin_all" ON projects.blocks;
|
|
CREATE POLICY "blocks_super_admin_all" ON projects.blocks FOR ALL TO authenticated
|
|
USING (public.get_current_user_role() = 'super_admin')
|
|
WITH CHECK (public.get_current_user_role() = 'super_admin');
|
|
|
|
DROP POLICY IF EXISTS "lots_super_admin_all" ON projects.lots;
|
|
CREATE POLICY "lots_super_admin_all" ON projects.lots FOR ALL TO authenticated
|
|
USING (public.get_current_user_role() = 'super_admin')
|
|
WITH CHECK (public.get_current_user_role() = 'super_admin');
|
|
|
|
DROP POLICY IF EXISTS "housing_units_super_admin_all" ON projects.housing_units;
|
|
CREATE POLICY "housing_units_super_admin_all" ON projects.housing_units FOR ALL TO authenticated
|
|
USING (public.get_current_user_role() = 'super_admin')
|
|
WITH CHECK (public.get_current_user_role() = 'super_admin');
|
|
|
|
-- ============================================================================
|
|
-- ÍNDICES PARA PERFORMANCE
|
|
-- ============================================================================
|
|
|
|
-- Stages
|
|
CREATE INDEX IF NOT EXISTS idx_stages_constructora_id ON projects.stages(constructora_id);
|
|
CREATE INDEX IF NOT EXISTS idx_stages_project_id ON projects.stages(project_id);
|
|
CREATE INDEX IF NOT EXISTS idx_stages_constructora_status ON projects.stages(constructora_id, status);
|
|
|
|
-- Blocks
|
|
CREATE INDEX IF NOT EXISTS idx_blocks_constructora_id ON projects.blocks(constructora_id);
|
|
CREATE INDEX IF NOT EXISTS idx_blocks_stage_id ON projects.blocks(stage_id);
|
|
CREATE INDEX IF NOT EXISTS idx_blocks_project_id ON projects.blocks(project_id);
|
|
|
|
-- Lots
|
|
CREATE INDEX IF NOT EXISTS idx_lots_constructora_id ON projects.lots(constructora_id);
|
|
CREATE INDEX IF NOT EXISTS idx_lots_stage_id ON projects.lots(stage_id);
|
|
CREATE INDEX IF NOT EXISTS idx_lots_block_id ON projects.lots(block_id);
|
|
CREATE INDEX IF NOT EXISTS idx_lots_project_id ON projects.lots(project_id);
|
|
CREATE INDEX IF NOT EXISTS idx_lots_constructora_status ON projects.lots(constructora_id, status);
|
|
CREATE INDEX IF NOT EXISTS idx_lots_prototype_id ON projects.lots(prototype_id) WHERE prototype_id IS NOT NULL;
|
|
|
|
-- Housing Units
|
|
CREATE INDEX IF NOT EXISTS idx_housing_units_constructora_id ON projects.housing_units(constructora_id);
|
|
CREATE INDEX IF NOT EXISTS idx_housing_units_lot_id ON projects.housing_units(lot_id);
|
|
CREATE INDEX IF NOT EXISTS idx_housing_units_project_id ON projects.housing_units(project_id);
|
|
CREATE INDEX IF NOT EXISTS idx_housing_units_status ON projects.housing_units(constructora_id, status);
|
|
|
|
COMMENT ON INDEX projects.idx_lots_constructora_id IS
|
|
'Optimiza queries RLS filtrados por constructora_id.';
|
|
|
|
COMMENT ON INDEX projects.idx_lots_constructora_status IS
|
|
'Optimiza query: listar lotes disponibles/vendidos de una constructora.';
|
|
|
|
-- ============================================================================
|
|
-- TRIGGERS PARA HEREDAR constructora_id
|
|
-- ============================================================================
|
|
|
|
-- Función: Auto-asignar constructora_id desde proyecto padre
|
|
CREATE OR REPLACE FUNCTION projects.auto_assign_constructora_id()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
-- Si no viene constructora_id, heredar del proyecto
|
|
IF NEW.constructora_id IS NULL THEN
|
|
SELECT constructora_id INTO NEW.constructora_id
|
|
FROM projects.projects
|
|
WHERE id = NEW.project_id;
|
|
|
|
IF NEW.constructora_id IS NULL THEN
|
|
RAISE EXCEPTION 'No se pudo determinar constructora_id para %', TG_TABLE_NAME;
|
|
END IF;
|
|
END IF;
|
|
|
|
-- Validar que constructora_id coincida con el proyecto
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM projects.projects
|
|
WHERE id = NEW.project_id
|
|
AND constructora_id = NEW.constructora_id
|
|
) THEN
|
|
RAISE EXCEPTION 'constructora_id no coincide con el proyecto padre';
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- Aplicar trigger a todas las tablas
|
|
DROP TRIGGER IF EXISTS trigger_auto_assign_constructora ON projects.stages;
|
|
CREATE TRIGGER trigger_auto_assign_constructora
|
|
BEFORE INSERT ON projects.stages
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION projects.auto_assign_constructora_id();
|
|
|
|
DROP TRIGGER IF EXISTS trigger_auto_assign_constructora ON projects.blocks;
|
|
CREATE TRIGGER trigger_auto_assign_constructora
|
|
BEFORE INSERT ON projects.blocks
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION projects.auto_assign_constructora_id();
|
|
|
|
DROP TRIGGER IF EXISTS trigger_auto_assign_constructora ON projects.lots;
|
|
CREATE TRIGGER trigger_auto_assign_constructora
|
|
BEFORE INSERT ON projects.lots
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION projects.auto_assign_constructora_id();
|
|
|
|
DROP TRIGGER IF EXISTS trigger_auto_assign_constructora ON projects.housing_units;
|
|
CREATE TRIGGER trigger_auto_assign_constructora
|
|
BEFORE INSERT ON projects.housing_units
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION projects.auto_assign_constructora_id();
|
|
|
|
COMMENT ON FUNCTION projects.auto_assign_constructora_id() IS
|
|
'Auto-asigna constructora_id heredando del proyecto padre.
|
|
Validación: Garantiza coherencia del discriminador multi-tenant.';
|
|
|
|
-- ============================================================================
|
|
-- CONSTRAINTS Y VALIDACIONES
|
|
-- ============================================================================
|
|
|
|
-- Asegurar que constructora_id no sea NULL
|
|
ALTER TABLE projects.stages ALTER COLUMN constructora_id SET NOT NULL;
|
|
ALTER TABLE projects.blocks ALTER COLUMN constructora_id SET NOT NULL;
|
|
ALTER TABLE projects.lots ALTER COLUMN constructora_id SET NOT NULL;
|
|
ALTER TABLE projects.housing_units ALTER COLUMN constructora_id SET NOT NULL;
|
|
|
|
-- Foreign Keys hacia constructoras
|
|
ALTER TABLE projects.stages
|
|
DROP CONSTRAINT IF EXISTS fk_stages_constructora,
|
|
ADD CONSTRAINT fk_stages_constructora
|
|
FOREIGN KEY (constructora_id) REFERENCES constructoras.constructoras(id) ON DELETE RESTRICT;
|
|
|
|
ALTER TABLE projects.blocks
|
|
DROP CONSTRAINT IF EXISTS fk_blocks_constructora,
|
|
ADD CONSTRAINT fk_blocks_constructora
|
|
FOREIGN KEY (constructora_id) REFERENCES constructoras.constructoras(id) ON DELETE RESTRICT;
|
|
|
|
ALTER TABLE projects.lots
|
|
DROP CONSTRAINT IF EXISTS fk_lots_constructora,
|
|
ADD CONSTRAINT fk_lots_constructora
|
|
FOREIGN KEY (constructora_id) REFERENCES constructoras.constructoras(id) ON DELETE RESTRICT;
|
|
|
|
ALTER TABLE projects.housing_units
|
|
DROP CONSTRAINT IF EXISTS fk_housing_units_constructora,
|
|
ADD CONSTRAINT fk_housing_units_constructora
|
|
FOREIGN KEY (constructora_id) REFERENCES constructoras.constructoras(id) ON DELETE RESTRICT;
|
|
|
|
-- ============================================================================
|
|
-- TESTS DE AISLAMIENTO RLS
|
|
-- ============================================================================
|
|
|
|
-- Test: Verificar que no se puede crear lote en proyecto de otra constructora
|
|
DO $$
|
|
DECLARE
|
|
v_constructora_a UUID := 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
|
v_constructora_b UUID := 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
|
|
v_project_a UUID;
|
|
v_stage_a UUID;
|
|
v_lot_id UUID;
|
|
BEGIN
|
|
-- Setup: Crear proyecto de constructora A
|
|
PERFORM set_config('app.current_constructora_id', v_constructora_a::TEXT, true);
|
|
PERFORM set_config('app.current_user_role', 'director', true);
|
|
|
|
INSERT INTO projects.projects (code, name, type, status, constructora_id)
|
|
VALUES ('TEST-A', 'Proyecto A', 'fraccionamiento', 'adjudicado', v_constructora_a)
|
|
RETURNING id INTO v_project_a;
|
|
|
|
INSERT INTO projects.stages (code, name, project_id, constructora_id)
|
|
VALUES ('ETA-1', 'Etapa 1', v_project_a, v_constructora_a)
|
|
RETURNING id INTO v_stage_a;
|
|
|
|
-- Cambiar contexto a constructora B (atacante)
|
|
PERFORM set_config('app.current_constructora_id', v_constructora_b::TEXT, true);
|
|
|
|
-- Intentar crear lote en proyecto de A (debe fallar)
|
|
BEGIN
|
|
INSERT INTO projects.lots (
|
|
code, number, stage_id, project_id, constructora_id
|
|
) VALUES (
|
|
'LOTE-1', '001', v_stage_a, v_project_a, v_constructora_b
|
|
) RETURNING id INTO v_lot_id;
|
|
|
|
RAISE EXCEPTION 'RLS FAIL: Se permitió crear lote en proyecto de otra constructora';
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
RAISE NOTICE 'Test RLS: PASSED - Cross-tenant INSERT bloqueado';
|
|
END;
|
|
|
|
-- Cleanup
|
|
PERFORM set_config('app.current_constructora_id', v_constructora_a::TEXT, true);
|
|
DELETE FROM projects.stages WHERE id = v_stage_a;
|
|
DELETE FROM projects.projects WHERE id = v_project_a;
|
|
END $$;
|
|
|
|
-- ============================================================================
|
|
-- GRANTS
|
|
-- ============================================================================
|
|
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON projects.stages TO authenticated;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON projects.blocks TO authenticated;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON projects.lots TO authenticated;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON projects.housing_units TO authenticated;
|
|
|
|
-- ============================================================================
|
|
-- VERIFICACIÓN FINAL
|
|
-- ============================================================================
|
|
|
|
DO $$
|
|
DECLARE
|
|
v_tables TEXT[] := ARRAY['stages', 'blocks', 'lots', 'housing_units'];
|
|
v_table TEXT;
|
|
v_rls_enabled BOOLEAN;
|
|
v_policy_count INTEGER;
|
|
BEGIN
|
|
FOREACH v_table IN ARRAY v_tables
|
|
LOOP
|
|
SELECT relrowsecurity INTO v_rls_enabled
|
|
FROM pg_class
|
|
WHERE relname = v_table AND relnamespace = 'projects'::regnamespace;
|
|
|
|
SELECT COUNT(*) INTO v_policy_count
|
|
FROM pg_policies
|
|
WHERE schemaname = 'projects' AND tablename = v_table;
|
|
|
|
IF NOT v_rls_enabled THEN
|
|
RAISE EXCEPTION 'CRITICAL: RLS no habilitado en projects.%', v_table;
|
|
END IF;
|
|
|
|
IF v_policy_count < 4 THEN
|
|
RAISE WARNING 'projects.%: Solo % políticas (esperadas: ≥4)', v_table, v_policy_count;
|
|
END IF;
|
|
|
|
RAISE NOTICE '✓ projects.%: RLS habilitado con % políticas', v_table, v_policy_count;
|
|
END LOOP;
|
|
|
|
RAISE NOTICE '✓ Todas las tablas tienen RLS habilitado correctamente';
|
|
END $$;
|