Overview
Quality Hub GINEZ uses Supabase (PostgreSQL 15) as its backend database with Row Level Security (RLS) enabled for all tables.
Main Tables
bitacora_produccion
The primary table for production batch records.
Primary key, auto-incrementing
created_at
timestamp with time zone
required
Timestamp when the record was created (automatic)
Batch/lot identifier in format: YYYYMMDD-SUC-PROD-###Example: 20240315-PAC1-LIMLIM-001
Product code (e.g., LIMLIM, TRALIM, SUASUE)Must match codes defined in PRODUCT_STANDARDS
Branch/location where product was manufacturedMust be one of 40 locations from SUCURSALES constant
Product family (e.g., “Cuidado del Hogar”, “Lavandería”)
Product category (e.g., “Limpiador liquido multiusos”)
Batch size in liters or kilogramsMust be a positive number
pH measurement (0-14)Only required for products in PH_STANDARDS
First solids percentage measurement (0-55%)Required for products with solids applicability
Temperature for first solids measurement
Second solids percentage measurement (0-55%)
Temperature for second solids measurement
Viscosity measurement in seconds
General temperature measurement
Visual appearance: “CRISTALINO”, “OPACO”, or “APERLADO”Must match APPEARANCE_STANDARDS for conformity
Color conformity: “CONFORME” or “NO CONFORME”
Aroma conformity: “CONFORME” or “NO CONFORME”
contaminacion_microbiologica
Microbiological contamination: “SIN PRESENCIA” or “CON PRESENCIA”
Additional observations (max 1000 characters)
Name of the operator who prepared the batch (max 200 characters)
Foreign key to auth.users(id)Identifies the user who created the record
profiles
User profile information and roles.
Primary key, references auth.users(id)
Whether the user has administrator privileges
Account creation timestamp
SQL Schema
Create bitacora_produccion Table
CREATE TABLE bitacora_produccion (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMPTZ DEFAULT NOW(),
lote_producto TEXT NOT NULL,
codigo_producto TEXT NOT NULL,
sucursal TEXT NOT NULL,
familia_producto TEXT,
categoria_producto TEXT,
fecha_fabricacion DATE NOT NULL,
tamano_lote NUMERIC NOT NULL,
ph NUMERIC,
solidos_medicion_1 NUMERIC,
temp_med1 NUMERIC,
solidos_medicion_2 NUMERIC,
temp_med2 NUMERIC,
viscosidad_seg NUMERIC,
temperatura NUMERIC,
apariencia TEXT NOT NULL,
color TEXT NOT NULL,
aroma TEXT NOT NULL,
contaminacion_microbiologica TEXT NOT NULL,
observaciones TEXT,
nombre_preparador TEXT NOT NULL,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE
);
-- Enable Row Level Security
ALTER TABLE bitacora_produccion ENABLE ROW LEVEL SECURITY;
-- Create index for faster queries
CREATE INDEX idx_bitacora_user_id ON bitacora_produccion(user_id);
CREATE INDEX idx_bitacora_fecha ON bitacora_produccion(fecha_fabricacion DESC);
CREATE INDEX idx_bitacora_producto ON bitacora_produccion(codigo_producto);
CREATE INDEX idx_bitacora_sucursal ON bitacora_produccion(sucursal);
Create profiles Table
CREATE TABLE profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
email TEXT,
nombre TEXT,
area TEXT,
puesto TEXT,
is_admin BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Enable Row Level Security
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
Row Level Security (RLS) Policies
bitacora_produccion Policies
-- Users can view their own records
CREATE POLICY "Users can view own records"
ON bitacora_produccion FOR SELECT
USING (auth.uid() = user_id);
-- Admins can view all records
CREATE POLICY "Admins can view all records"
ON bitacora_produccion FOR SELECT
USING (
EXISTS (
SELECT 1 FROM profiles
WHERE profiles.id = auth.uid()
AND profiles.is_admin = true
)
);
-- Users can insert their own records
CREATE POLICY "Users can insert own records"
ON bitacora_produccion FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- Users can update their own records
CREATE POLICY "Users can update own records"
ON bitacora_produccion FOR UPDATE
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- Admins can update all records
CREATE POLICY "Admins can update all records"
ON bitacora_produccion FOR UPDATE
USING (
EXISTS (
SELECT 1 FROM profiles
WHERE profiles.id = auth.uid()
AND profiles.is_admin = true
)
)
WITH CHECK (true);
-- Only admins can delete records
CREATE POLICY "Admins can delete records"
ON bitacora_produccion FOR DELETE
USING (
EXISTS (
SELECT 1 FROM profiles
WHERE profiles.id = auth.uid()
AND profiles.is_admin = true
)
);
profiles Policies
-- Users can view their own profile
CREATE POLICY "Users can view own profile"
ON profiles FOR SELECT
USING (auth.uid() = id);
-- Admins can view all profiles
CREATE POLICY "Admins can view all profiles"
ON profiles FOR SELECT
USING (
EXISTS (
SELECT 1 FROM profiles p
WHERE p.id = auth.uid()
AND p.is_admin = true
)
);
-- Users can update their own profile (except is_admin)
CREATE POLICY "Users can update own profile"
ON profiles FOR UPDATE
USING (auth.uid() = id)
WITH CHECK (auth.uid() = id);
-- Only admins can change roles
CREATE POLICY "Admins can update all profiles"
ON profiles FOR UPDATE
USING (
EXISTS (
SELECT 1 FROM profiles p
WHERE p.id = auth.uid()
AND p.is_admin = true
)
)
WITH CHECK (true);
Environment Variables
NEXT_PUBLIC_SUPABASE_URL="https://your-project.supabase.co"
NEXT_PUBLIC_SUPABASE_ANON_KEY="your-anon-key"
Client Configuration
File: lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
export const supabase = createClient(supabaseUrl, supabaseKey)
Data Integrity
Referential Integrity
user_id in bitacora_produccion references auth.users(id) with ON DELETE CASCADE
- When a user is deleted, their production records are preserved but the foreign key is nullified
Constraints
- All
NOT NULL fields must be provided
- Numeric fields accept NULL for optional measurements
- Text fields have maximum lengths enforced at the application level
Source Code Reference
Database Schema Documentation: README.md:85-103
RLS Policies Documentation: README.md:329-347