-- ================================================================ -- SCHEMA - App Recolección Inteligente · Hackathon Celaya 2026 -- ================================================================ CREATE EXTENSION IF NOT EXISTS postgis; CREATE EXTENSION IF NOT EXISTS pgcrypto; -- ---------------------------------------------------------------- -- 1. USERS -- ---------------------------------------------------------------- CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) UNIQUE, phone VARCHAR(20) UNIQUE, password_hash VARCHAR(255) NOT NULL, fcm_token VARCHAR(500), rol VARCHAR(20) NOT NULL DEFAULT 'ciudadano', activo BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT chk_contacto CHECK (email IS NOT NULL OR phone IS NOT NULL) ); -- ---------------------------------------------------------------- -- 2. RUTAS -- ---------------------------------------------------------------- CREATE TABLE rutas ( id SERIAL PRIMARY KEY, route_id VARCHAR(20) NOT NULL UNIQUE, nombre VARCHAR(255) NOT NULL, truck_id SMALLINT NOT NULL, hora_inicio TIME NOT NULL, hora_fin_ref TIME NOT NULL, activa BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- ---------------------------------------------------------------- -- 3. RUTA_POSICIONES -- ---------------------------------------------------------------- CREATE TABLE ruta_posiciones ( id BIGSERIAL PRIMARY KEY, ruta_id INT NOT NULL REFERENCES rutas(id) ON DELETE CASCADE, position_id SMALLINT NOT NULL, lat DECIMAL(9,6) NOT NULL, lng DECIMAL(9,6) NOT NULL, speed_kmh SMALLINT NOT NULL DEFAULT 0, ts_referencia TIMESTAMPTZ NOT NULL, offset_seg INT NOT NULL DEFAULT 0, punto GEOGRAPHY(POINT, 4326) NOT NULL, CONSTRAINT uq_ruta_posicion UNIQUE (ruta_id, position_id) ); CREATE INDEX idx_ruta_pos_ruta ON ruta_posiciones (ruta_id, position_id); CREATE INDEX idx_ruta_pos_punto ON ruta_posiciones USING GIST (punto); -- ---------------------------------------------------------------- -- 4. COLONIAS -- ---------------------------------------------------------------- CREATE TABLE colonias ( id SERIAL PRIMARY KEY, nombre VARCHAR(255) NOT NULL UNIQUE, ruta_id INT NOT NULL REFERENCES rutas(id), horario_turno VARCHAR(20) NOT NULL, hora_inicio_est TIME NOT NULL, hora_fin_est TIME NOT NULL, poligono GEOGRAPHY(POLYGON, 4326), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_colonias_ruta ON colonias (ruta_id); CREATE INDEX idx_colonias_pol ON colonias USING GIST (poligono) WHERE poligono IS NOT NULL; -- ---------------------------------------------------------------- -- 5. DOMICILIOS -- ---------------------------------------------------------------- CREATE TABLE domicilios ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, alias VARCHAR(50) NOT NULL, direccion_texto TEXT NOT NULL, lat DECIMAL(9,6) NOT NULL, lng DECIMAL(9,6) NOT NULL, coordenada GEOGRAPHY(POINT, 4326) NOT NULL, colonia_id INT REFERENCES colonias(id) ON DELETE SET NULL, validado BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uq_domicilio_alias UNIQUE (user_id, alias) ); CREATE INDEX idx_domicilios_user ON domicilios (user_id); CREATE INDEX idx_domicilios_col ON domicilios (colonia_id); CREATE INDEX idx_domicilios_coord ON domicilios USING GIST (coordenada); -- ---------------------------------------------------------------- -- 6. ESTADO_RUTA (actualizado por el simulador/cron) -- ---------------------------------------------------------------- CREATE TABLE estado_ruta ( ruta_id INT PRIMARY KEY REFERENCES rutas(id) ON DELETE CASCADE, estado VARCHAR(20) NOT NULL DEFAULT 'INACTIVA', position_id_actual SMALLINT NOT NULL DEFAULT 1, lat_actual DECIMAL(9,6), lng_actual DECIMAL(9,6), hora_real_inicio TIMESTAMPTZ, hora_estim_fin TIMESTAMPTZ, minutos_retraso SMALLINT NOT NULL DEFAULT 0, motivo_retraso TEXT, actualizado_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- ---------------------------------------------------------------- -- 7. NOTIFICACION_TEMPLATES -- ---------------------------------------------------------------- CREATE TABLE notificacion_templates ( id SERIAL PRIMARY KEY, trigger_event VARCHAR(30) NOT NULL UNIQUE, position_id_trigger SMALLINT NOT NULL, titulo VARCHAR(150) NOT NULL, cuerpo TEXT NOT NULL, activo BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- ---------------------------------------------------------------- -- 8. NOTIFICACIONES -- ---------------------------------------------------------------- CREATE TABLE notificaciones ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, domicilio_id UUID REFERENCES domicilios(id) ON DELETE SET NULL, template_id INT REFERENCES notificacion_templates(id), ruta_id INT REFERENCES rutas(id), titulo VARCHAR(150) NOT NULL, mensaje TEXT NOT NULL, enviada_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), leida BOOLEAN NOT NULL DEFAULT FALSE, leida_at TIMESTAMPTZ ); CREATE INDEX idx_notif_user ON notificaciones (user_id, enviada_at DESC); CREATE INDEX idx_notif_no_leida ON notificaciones (user_id) WHERE leida = FALSE; -- ---------------------------------------------------------------- -- 9. REPORTES -- ---------------------------------------------------------------- CREATE TABLE reportes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, domicilio_id UUID REFERENCES domicilios(id) ON DELETE SET NULL, ruta_id INT REFERENCES rutas(id), tipo VARCHAR(40) NOT NULL, descripcion TEXT, calificacion SMALLINT CHECK (calificacion BETWEEN 1 AND 5), estado VARCHAR(20) NOT NULL DEFAULT 'abierto', creado_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_reportes_user ON reportes (user_id); CREATE INDEX idx_reportes_ruta ON reportes (ruta_id, creado_at); -- ---------------------------------------------------------------- -- 10. GUÍA DE SEPARACIÓN -- ---------------------------------------------------------------- CREATE TABLE residuos_categorias ( id SERIAL PRIMARY KEY, nombre VARCHAR(50) NOT NULL, descripcion TEXT, color_hex CHAR(7), icono VARCHAR(100) ); CREATE TABLE residuos_ejemplos ( id SERIAL PRIMARY KEY, categoria_id INT NOT NULL REFERENCES residuos_categorias(id) ON DELETE CASCADE, nombre VARCHAR(255) NOT NULL, descripcion TEXT ); -- ---------------------------------------------------------------- -- TRIGGERS -- ---------------------------------------------------------------- CREATE OR REPLACE FUNCTION fn_set_updated_at() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$; CREATE TRIGGER trg_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION fn_set_updated_at();