docs, css, database: Complete Phase 1 - Foundation & Database Configuration
This commit is contained in:
parent
34f1a0d3d1
commit
f6b8f019cd
3 changed files with 350 additions and 5 deletions
|
|
@ -7,14 +7,14 @@
|
|||
This plan outlines the step-by-step path to construct, test, and host the platform securely.
|
||||
|
||||
## Phase 1: Foundation & Database Configuration
|
||||
* [ ] Initialize Next.js app with TypeScript and `npx` in the repository root.
|
||||
* [ ] Configure Vanilla CSS design tokens (variables, layout standards, light/dark themes).
|
||||
* [ ] Define the PostgreSQL schemas in `schema.prisma` including:
|
||||
* [x] Initialize Next.js app with TypeScript and `npx` in the repository root.
|
||||
* [x] Configure Vanilla CSS design tokens (variables, layout standards, light/dark themes).
|
||||
* [x] Define the PostgreSQL schemas in `schema.prisma` including:
|
||||
* Temporal versioning fields for plans (`version`, `validity_start`, `validity_end`).
|
||||
* Idempotency and transaction fields for sales imports.
|
||||
* Adjustment and original reference keys for settlements.
|
||||
* [ ] Configure **PostgreSQL Row-Level Security (RLS)** policies on the schema for user/hotel data isolation.
|
||||
* [ ] Execute initial database migration to seed basic structural tables (Regions, Hotels, Roles).
|
||||
* [x] Configure **PostgreSQL Row-Level Security (RLS)** policies on the schema for user/hotel data isolation.
|
||||
* [x] Execute initial database migration to seed basic structural tables (Regions, Hotels, Roles).
|
||||
|
||||
## Phase 2: Authentication & Security Core
|
||||
* [ ] Implement secure JWT session cookie-based auth.
|
||||
|
|
|
|||
213
prisma/rls_and_seed.sql
Normal file
213
prisma/rls_and_seed.sql
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
-- RLS Policies and Seeding Script for Hoteles Estelar Variable Remuneration System
|
||||
|
||||
-- ==========================================
|
||||
-- 1. SEED DATA
|
||||
-- ==========================================
|
||||
|
||||
-- Seed Regions
|
||||
INSERT INTO "regions" ("name", "code") VALUES
|
||||
('Bogotá', 'BOG'),
|
||||
('Antioquia', 'ANT'),
|
||||
('Caribe', 'CAR')
|
||||
ON CONFLICT ("code") DO NOTHING;
|
||||
|
||||
-- Seed Hotels
|
||||
INSERT INTO "hotels" ("name", "code", "region_id", "status") VALUES
|
||||
('Estelar Parque de la 93', 'EST-P93', (SELECT id FROM regions WHERE code = 'BOG'), 'ACTIVE'),
|
||||
('Estelar Medellin', 'EST-MDE', (SELECT id FROM regions WHERE code = 'ANT'), 'ACTIVE'),
|
||||
('Estelar Cartagena', 'EST-CTG', (SELECT id FROM regions WHERE code = 'CAR'), 'ACTIVE')
|
||||
ON CONFLICT ("code") DO NOTHING;
|
||||
|
||||
-- Seed Users
|
||||
-- password_hash is bcrypt hash of 'password123'
|
||||
INSERT INTO "users" ("username", "email", "password_hash", "role", "hotel_id", "area", "status", "created_at") VALUES
|
||||
('admin', 'admin@estelar.com', '$2b$10$EpjJNrk.wU5YzB3sJsmJg.35A5EJUPhq31O7p8HwS5zM4pU1fT26G', 'ADMIN', (SELECT id FROM hotels WHERE code = 'EST-P93'), 'Sistemas', 'ACTIVE', NOW()),
|
||||
('director', 'director@estelar.com', '$2b$10$EpjJNrk.wU5YzB3sJsmJg.35A5EJUPhq31O7p8HwS5zM4pU1fT26G', 'DIRECTOR', (SELECT id FROM hotels WHERE code = 'EST-P93'), 'Comercial', 'ACTIVE', NOW()),
|
||||
('gerente_mde', 'gerente.mde@estelar.com', '$2b$10$EpjJNrk.wU5YzB3sJsmJg.35A5EJUPhq31O7p8HwS5zM4pU1fT26G', 'GERENTE', (SELECT id FROM hotels WHERE code = 'EST-MDE'), 'Administracion', 'ACTIVE', NOW()),
|
||||
('lider_ctg', 'lider.ctg@estelar.com', '$2b$10$EpjJNrk.wU5YzB3sJsmJg.35A5EJUPhq31O7p8HwS5zM4pU1fT26G', 'LIDER', (SELECT id FROM hotels WHERE code = 'EST-CTG'), 'Ventas', 'ACTIVE', NOW()),
|
||||
('analista', 'analista@estelar.com', '$2b$10$EpjJNrk.wU5YzB3sJsmJg.35A5EJUPhq31O7p8HwS5zM4pU1fT26G', 'ANALISTA', (SELECT id FROM hotels WHERE code = 'EST-P93'), 'Finanzas', 'ACTIVE', NOW()),
|
||||
('consulta', 'consulta@estelar.com', '$2b$10$EpjJNrk.wU5YzB3sJsmJg.35A5EJUPhq31O7p8HwS5zM4pU1fT26G', 'CONSULTA', (SELECT id FROM hotels WHERE code = 'EST-P93'), 'Auditoria', 'ACTIVE', NOW()),
|
||||
('colaborador_mde', 'colaborador.mde@estelar.com', '$2b$10$EpjJNrk.wU5YzB3sJsmJg.35A5EJUPhq31O7p8HwS5zM4pU1fT26G', 'COLABORADOR', (SELECT id FROM hotels WHERE code = 'EST-MDE'), 'Ventas', 'ACTIVE', NOW())
|
||||
ON CONFLICT ("username") DO NOTHING;
|
||||
|
||||
|
||||
-- ==========================================
|
||||
-- 2. ENABLE ROW-LEVEL SECURITY
|
||||
-- ==========================================
|
||||
|
||||
ALTER TABLE "regions" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "hotels" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "goals" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "sales_results" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "settlements" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "audit_logs" ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
ALTER TABLE "regions" FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "hotels" FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "users" FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "goals" FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "sales_results" FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "settlements" FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "audit_logs" FORCE ROW LEVEL SECURITY;
|
||||
|
||||
|
||||
-- ==========================================
|
||||
-- 3. CLEAN UP OLD POLICIES
|
||||
-- ==========================================
|
||||
|
||||
DROP POLICY IF EXISTS audit_logs_insert_policy ON "audit_logs";
|
||||
DROP POLICY IF EXISTS audit_logs_select_policy ON "audit_logs";
|
||||
DROP POLICY IF EXISTS regions_select_policy ON "regions";
|
||||
DROP POLICY IF EXISTS regions_modify_policy ON "regions";
|
||||
DROP POLICY IF EXISTS hotels_select_policy ON "hotels";
|
||||
DROP POLICY IF EXISTS hotels_modify_policy ON "hotels";
|
||||
DROP POLICY IF EXISTS users_select_policy ON "users";
|
||||
DROP POLICY IF EXISTS users_modify_policy ON "users";
|
||||
DROP POLICY IF EXISTS goals_select_policy ON "goals";
|
||||
DROP POLICY IF EXISTS goals_modify_policy ON "goals";
|
||||
DROP POLICY IF EXISTS sales_results_select_policy ON "sales_results";
|
||||
DROP POLICY IF EXISTS sales_results_modify_policy ON "sales_results";
|
||||
DROP POLICY IF EXISTS settlements_select_policy ON "settlements";
|
||||
DROP POLICY IF EXISTS settlements_modify_policy ON "settlements";
|
||||
DROP POLICY IF EXISTS plans_select_policy ON "compensation_plans";
|
||||
DROP POLICY IF EXISTS plans_modify_policy ON "compensation_plans";
|
||||
DROP POLICY IF EXISTS rules_select_policy ON "calculation_rules";
|
||||
DROP POLICY IF EXISTS rules_modify_policy ON "calculation_rules";
|
||||
|
||||
|
||||
-- ==========================================
|
||||
-- 4. CREATE RLS POLICIES
|
||||
-- ==========================================
|
||||
|
||||
-- A. Audit Logs Policies (Insert allowed for all, Select only for Admin/Analista, No Updates/Deletes)
|
||||
CREATE POLICY audit_logs_insert_policy ON "audit_logs"
|
||||
FOR INSERT WITH CHECK (true);
|
||||
|
||||
CREATE POLICY audit_logs_select_policy ON "audit_logs"
|
||||
FOR SELECT USING (current_setting('app.current_user_role', true) IN ('ADMIN', 'ANALISTA'));
|
||||
|
||||
|
||||
-- B. Regions Policies
|
||||
CREATE POLICY regions_select_policy ON "regions"
|
||||
FOR SELECT USING (
|
||||
current_setting('app.current_user_role', true) IN ('ADMIN', 'ANALISTA', 'DIRECTOR')
|
||||
OR id = NULLIF(current_setting('app.current_region_id', true), '')::integer
|
||||
);
|
||||
|
||||
CREATE POLICY regions_modify_policy ON "regions"
|
||||
FOR ALL USING (current_setting('app.current_user_role', true) IN ('ADMIN', 'DIRECTOR'));
|
||||
|
||||
|
||||
-- C. Hotels Policies
|
||||
CREATE POLICY hotels_select_policy ON "hotels"
|
||||
FOR SELECT USING (
|
||||
current_setting('app.current_user_role', true) IN ('ADMIN', 'ANALISTA', 'DIRECTOR')
|
||||
OR region_id = NULLIF(current_setting('app.current_region_id', true), '')::integer
|
||||
OR id = NULLIF(current_setting('app.current_hotel_id', true), '')::integer
|
||||
);
|
||||
|
||||
CREATE POLICY hotels_modify_policy ON "hotels"
|
||||
FOR ALL USING (current_setting('app.current_user_role', true) IN ('ADMIN', 'DIRECTOR'));
|
||||
|
||||
|
||||
-- D. Users Policies
|
||||
CREATE POLICY users_select_policy ON "users"
|
||||
FOR SELECT USING (
|
||||
current_setting('app.current_user_role', true) IN ('ADMIN', 'ANALISTA', 'DIRECTOR')
|
||||
OR hotel_id = NULLIF(current_setting('app.current_hotel_id', true), '')::integer
|
||||
OR hotel_id IN (SELECT id FROM hotels WHERE region_id = NULLIF(current_setting('app.current_region_id', true), '')::integer)
|
||||
OR id = NULLIF(current_setting('app.current_user_id', true), '')::integer
|
||||
);
|
||||
|
||||
CREATE POLICY users_modify_policy ON "users"
|
||||
FOR ALL USING (current_setting('app.current_user_role', true) = 'ADMIN');
|
||||
|
||||
|
||||
-- E. Goals Policies
|
||||
CREATE POLICY goals_select_policy ON "goals"
|
||||
FOR SELECT USING (
|
||||
current_setting('app.current_user_role', true) IN ('ADMIN', 'ANALISTA', 'DIRECTOR')
|
||||
OR target_id = NULLIF(current_setting('app.current_user_id', true), '')::integer
|
||||
OR target_id IN (
|
||||
SELECT u.id FROM users u WHERE u.hotel_id = NULLIF(current_setting('app.current_hotel_id', true), '')::integer
|
||||
)
|
||||
OR target_id IN (
|
||||
SELECT u.id FROM users u JOIN hotels h ON u.hotel_id = h.id
|
||||
WHERE h.region_id = NULLIF(current_setting('app.current_region_id', true), '')::integer
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY goals_modify_policy ON "goals"
|
||||
FOR ALL USING (current_setting('app.current_user_role', true) IN ('ADMIN', 'DIRECTOR'));
|
||||
|
||||
|
||||
-- F. Sales Results Policies
|
||||
CREATE POLICY sales_results_select_policy ON "sales_results"
|
||||
FOR SELECT USING (
|
||||
current_setting('app.current_user_role', true) IN ('ADMIN', 'ANALISTA', 'DIRECTOR')
|
||||
OR user_id = NULLIF(current_setting('app.current_user_id', true), '')::integer
|
||||
OR hotel_id = NULLIF(current_setting('app.current_hotel_id', true), '')::integer
|
||||
OR hotel_id IN (
|
||||
SELECT id FROM hotels WHERE region_id = NULLIF(current_setting('app.current_region_id', true), '')::integer
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY sales_results_modify_policy ON "sales_results"
|
||||
FOR ALL USING (
|
||||
current_setting('app.current_user_role', true) IN ('ADMIN', 'ANALISTA')
|
||||
OR (
|
||||
current_setting('app.current_user_role', true) = 'LIDER'
|
||||
AND hotel_id IN (
|
||||
SELECT id FROM hotels WHERE region_id = NULLIF(current_setting('app.current_region_id', true), '')::integer
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
-- G. Settlements Policies
|
||||
CREATE POLICY settlements_select_policy ON "settlements"
|
||||
FOR SELECT USING (
|
||||
current_setting('app.current_user_role', true) IN ('ADMIN', 'ANALISTA', 'DIRECTOR')
|
||||
OR user_id = NULLIF(current_setting('app.current_user_id', true), '')::integer
|
||||
OR user_id IN (
|
||||
SELECT u.id FROM users u WHERE u.hotel_id = NULLIF(current_setting('app.current_hotel_id', true), '')::integer
|
||||
)
|
||||
OR user_id IN (
|
||||
SELECT u.id FROM users u JOIN hotels h ON u.hotel_id = h.id
|
||||
WHERE h.region_id = NULLIF(current_setting('app.current_region_id', true), '')::integer
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY settlements_modify_policy ON "settlements"
|
||||
FOR ALL USING (
|
||||
current_setting('app.current_user_role', true) IN ('ADMIN', 'ANALISTA')
|
||||
OR (
|
||||
current_setting('app.current_user_role', true) = 'LIDER'
|
||||
AND user_id IN (
|
||||
SELECT u.id FROM users u JOIN hotels h ON u.hotel_id = h.id
|
||||
WHERE h.region_id = NULLIF(current_setting('app.current_region_id', true), '')::integer
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
-- H. Compensation Plans Policies
|
||||
ALTER TABLE "compensation_plans" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "compensation_plans" FORCE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY plans_select_policy ON "compensation_plans"
|
||||
FOR SELECT USING (true);
|
||||
|
||||
CREATE POLICY plans_modify_policy ON "compensation_plans"
|
||||
FOR ALL USING (current_setting('app.current_user_role', true) IN ('ADMIN', 'DIRECTOR', 'ANALISTA'));
|
||||
|
||||
|
||||
-- I. Calculation Rules Policies
|
||||
ALTER TABLE "calculation_rules" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "calculation_rules" FORCE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY rules_select_policy ON "calculation_rules"
|
||||
FOR SELECT USING (true);
|
||||
|
||||
CREATE POLICY rules_modify_policy ON "calculation_rules"
|
||||
FOR ALL USING (current_setting('app.current_user_role', true) IN ('ADMIN', 'DIRECTOR', 'ANALISTA'));
|
||||
132
src/app/globals.css
Normal file
132
src/app/globals.css
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
:root {
|
||||
/* Color Palette - HSL values for premium dark/light mode */
|
||||
--primary-h: 220;
|
||||
--primary-s: 85%;
|
||||
--primary-l: 57%;
|
||||
--primary: hsl(var(--primary-h), var(--primary-s), var(--primary-l));
|
||||
|
||||
--secondary-h: 260;
|
||||
--secondary-s: 70%;
|
||||
--secondary-l: 50%;
|
||||
--secondary: hsl(var(--secondary-h), var(--secondary-s), var(--secondary-l));
|
||||
|
||||
--background-h: 0;
|
||||
--background-s: 0%;
|
||||
--background-l: 100%;
|
||||
--background: hsl(var(--background-h), var(--background-s), var(--background-l));
|
||||
|
||||
--foreground-h: 220;
|
||||
--foreground-s: 40%;
|
||||
--foreground-l: 10%;
|
||||
--foreground: hsl(var(--foreground-h), var(--foreground-s), var(--foreground-l));
|
||||
|
||||
--card: hsl(0, 0%, 97%);
|
||||
--border: hsl(220, 20%, 90%);
|
||||
|
||||
/* Spacing Grid (8pt Grid System) */
|
||||
--space-1: 0.25rem; /* 4px */
|
||||
--space-2: 0.5rem; /* 8px */
|
||||
--space-3: 0.75rem; /* 12px */
|
||||
--space-4: 1rem; /* 16px */
|
||||
--space-6: 1.5rem; /* 24px */
|
||||
--space-8: 2rem; /* 32px */
|
||||
--space-12: 3rem; /* 48px */
|
||||
|
||||
/* Typography Scale */
|
||||
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--weight-normal: 400;
|
||||
--weight-medium: 500;
|
||||
--weight-semibold: 600;
|
||||
--weight-bold: 700;
|
||||
|
||||
/* Border Radius & Shadow Tokens */
|
||||
--radius-sm: 0.375rem;
|
||||
--radius-md: 0.5rem;
|
||||
--radius-lg: 0.75rem;
|
||||
--radius-xl: 1rem;
|
||||
--radius-full: 9999px;
|
||||
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
||||
--shadow-glow: 0 0 15px 2px hsla(var(--primary-h), var(--primary-s), var(--primary-l), 0.15);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--transition-normal: 250ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Dark mode overrides (Activated via html[data-theme='dark'] or prefers-color-scheme) */
|
||||
[data-theme='dark'] {
|
||||
--background-h: 220;
|
||||
--background-s: 40%;
|
||||
--background-l: 6%;
|
||||
--background: hsl(var(--background-h), var(--background-s), var(--background-l));
|
||||
|
||||
--foreground-h: 220;
|
||||
--foreground-s: 15%;
|
||||
--foreground-l: 90%;
|
||||
--foreground: hsl(var(--foreground-h), var(--foreground-s), var(--foreground-l));
|
||||
|
||||
--card: hsl(220, 30%, 11%);
|
||||
--border: hsl(220, 20%, 18%);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme='light']) {
|
||||
--background-h: 220;
|
||||
--background-s: 40%;
|
||||
--background-l: 6%;
|
||||
--background: hsl(var(--background-h), var(--background-s), var(--background-l));
|
||||
|
||||
--foreground-h: 220;
|
||||
--foreground-s: 15%;
|
||||
--foreground-l: 90%;
|
||||
--foreground: hsl(var(--foreground-h), var(--foreground-s), var(--foreground-l));
|
||||
|
||||
--card: hsl(220, 30%, 11%);
|
||||
--border: hsl(220, 20%, 18%);
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
Loading…
Reference in a new issue