feat: migrate backend from SQLite to Supabase PostgreSQL

This commit is contained in:
Alan Alonso
2026-05-23 00:55:00 -06:00
parent 327852e468
commit ff90f3eefc
4 changed files with 192 additions and 235 deletions

View File

@@ -1,142 +1,82 @@
"""
Base de datos SQLite — esquema unificado con Persona A.
Tablas propias del módulo B: truck_status, notificaciones, ws_sessions.
Base de datos Supabase PostgreSQL — conexión y utilidades.
"""
import sqlite3
from pathlib import Path
import os
from supabase import create_client, Client
from dotenv import load_dotenv
DB_PATH = Path("basura.db")
load_dotenv()
SUPABASE_URL = os.getenv("SUPABASE_URL", "https://qckndtzudciejpnwqfzt.supabase.co")
SUPABASE_KEY = os.getenv("SUPABASE_ANON_KEY", "sb_publishable_FQR0WXK6joM043Qve9gz3A_pJfAH...")
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
def get_connection() -> sqlite3.Connection:
conn = sqlite3.connect(DB_PATH, check_same_thread=False)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")
return conn
def get_db() -> Client:
"""Retorna cliente Supabase."""
return supabase
def init_db() -> None:
conn = get_connection()
conn.executescript("""
-- ── Tablas de Persona A (las creamos aquí para que el módulo B
-- pueda leerlas aunque A no haya corrido aún) ──────────────
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
phone TEXT,
password_hash TEXT NOT NULL,
fcm_token TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS addresses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
alias TEXT,
lat REAL NOT NULL,
lng REAL NOT NULL,
route_id TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS notification_preferences (
user_id INTEGER PRIMARY KEY,
notify_proximity BOOLEAN DEFAULT 1,
notify_breakdown BOOLEAN DEFAULT 1,
notify_delay BOOLEAN DEFAULT 1,
notify_route_start BOOLEAN DEFAULT 1,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS notification_templates (
id INTEGER PRIMARY KEY,
trigger_event TEXT UNIQUE,
title TEXT,
body TEXT
);
-- ── Tablas del módulo B ───────────────────────────────────────
CREATE TABLE IF NOT EXISTS truck_status (
route_id TEXT PRIMARY KEY,
current_position_id INTEGER DEFAULT 1,
last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status TEXT DEFAULT 'EN_RUTA'
);
CREATE TABLE IF NOT EXISTS rutas (
id TEXT PRIMARY KEY,
nombre TEXT NOT NULL,
turno TEXT NOT NULL DEFAULT 'mañana'
);
CREATE TABLE IF NOT EXISTS puntos_ruta (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ruta_id TEXT NOT NULL REFERENCES rutas(id),
orden INTEGER NOT NULL,
nombre TEXT NOT NULL,
lat REAL NOT NULL,
lng REAL NOT NULL,
tiempo_estimado_min INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS notificaciones (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tipo TEXT NOT NULL,
ruta_id TEXT NOT NULL,
address_id INTEGER,
mensaje TEXT NOT NULL,
eta_minutos INTEGER,
creada_en TEXT NOT NULL
);
""")
conn.commit()
conn.close()
_seed_datos_demo()
async def init_db() -> None:
"""
Inicializa BD. En Supabase, tablas ya existen en el schema_supabase.sql.
Esta función valida conexión y seed data si es necesario.
"""
try:
# Valida conexión leyendo rutas
result = supabase.table("rutas").select("id").eq("id", "RUTA-01").execute()
if not result.data:
# Seed data si no existe
_seed_datos_demo()
print("✓ BD Supabase inicializada")
except Exception as e:
print(f"✗ Error inicialización BD: {e}")
raise
def _seed_datos_demo() -> None:
conn = get_connection()
existe = conn.execute("SELECT 1 FROM rutas WHERE id='RUTA-01'").fetchone()
if existe:
conn.close()
return
"""Inserta datos demo si no existen."""
try:
# Rutas
supabase.table("rutas").insert({
"id": "RUTA-01",
"nombre": "Ruta 01 — Sector Centro",
"turno": "mañana"
}).execute()
conn.executescript("""
-- Ruta de demo (Celaya, Guanajuato)
INSERT INTO rutas VALUES ('RUTA-01', 'Ruta 01 — Sector Centro', 'mañana');
# Puntos ruta
puntos = [
{"ruta_id": "RUTA-01", "orden": 1, "nombre": "Estación Central", "lat": 20.5238, "lng": -100.8143, "tiempo_estimado_min": 0},
{"ruta_id": "RUTA-01", "orden": 2, "nombre": "Col. Independencia", "lat": 20.5255, "lng": -100.8090, "tiempo_estimado_min": 8},
{"ruta_id": "RUTA-01", "orden": 3, "nombre": "Blvd. A. López Mateos", "lat": 20.5271, "lng": -100.8021, "tiempo_estimado_min": 18},
{"ruta_id": "RUTA-01", "orden": 4, "nombre": "Col. Jardines del Bosque", "lat": 20.5290, "lng": -100.7965, "tiempo_estimado_min": 28},
{"ruta_id": "RUTA-01", "orden": 5, "nombre": "Mercado Hidalgo", "lat": 20.5310, "lng": -100.7910, "tiempo_estimado_min": 38},
]
for punto in puntos:
supabase.table("puntos_ruta").insert(punto).execute()
INSERT INTO puntos_ruta (ruta_id, orden, nombre, lat, lng, tiempo_estimado_min)
VALUES
('RUTA-01', 1, 'Estación Central', 20.5238, -100.8143, 0),
('RUTA-01', 2, 'Col. Independencia', 20.5255, -100.8090, 8),
('RUTA-01', 3, 'Blvd. A. López Mateos', 20.5271, -100.8021, 18),
('RUTA-01', 4, 'Col. Jardines del Bosque', 20.5290, -100.7965, 28),
('RUTA-01', 5, 'Mercado Hidalgo', 20.5310, -100.7910, 38);
# Truck status
supabase.table("truck_status").insert({
"route_id": "RUTA-01",
"current_position_id": 1,
"status": "EN_RUTA"
}).execute()
INSERT INTO truck_status VALUES ('RUTA-01', 1, CURRENT_TIMESTAMP, 'EN_RUTA');
# Notification templates
templates = [
{"trigger_event": "ruta_iniciada", "title": "Ruta iniciada", "body": "El camión ha comenzado su ruta. Prepárate."},
{"trigger_event": "aproximandose", "title": "¡Camión cerca!", "body": "El camión llega en ~{eta} minutos. Saca tu basura."},
{"trigger_event": "falla_mecanica", "title": "Aviso de servicio", "body": "El camión reportó una falla. Te notificaremos cuando se reanude."},
{"trigger_event": "ruta_tarde", "title": "Cambio de horario", "body": "El camión de la mañana pasará en el turno de la tarde."},
{"trigger_event": "completado", "title": "Ruta completada", "body": "El camión completó su paso por tu zona. ¡Hasta mañana!"},
]
for template in templates:
try:
supabase.table("notification_templates").insert(template).execute()
except:
pass # Ignorar duplicados
-- Usuario de demo
INSERT INTO users (email, phone, password_hash)
VALUES ('demo@basura.app', '4611234567', 'hashed_demo');
-- Domicilio de demo asignado a RUTA-01
INSERT INTO addresses (user_id, alias, lat, lng, route_id)
VALUES (1, 'Casa', 20.5285, -100.7980, 'RUTA-01');
-- Preferencias por defecto para usuario demo
INSERT INTO notification_preferences VALUES (1, 1, 1, 1, 1);
-- Templates de notificación
INSERT INTO notification_templates (trigger_event, title, body) VALUES
('ruta_iniciada', 'Ruta iniciada', 'El camión ha comenzado su ruta. Prepárate.'),
('aproximandose', '¡Camión cerca!', 'El camión llega en ~{eta} minutos. Saca tu basura.'),
('falla_mecanica', 'Aviso de servicio', 'El camión reportó una falla. Te notificaremos cuando se reanude.'),
('ruta_tarde', 'Cambio de horario', 'El camión de la mañana pasará en el turno de la tarde.'),
('completado', 'Ruta completada', 'El camión completó su paso por tu zona. ¡Hasta mañana!');
""")
conn.commit()
conn.close()
print("✓ Datos demo insertados")
except Exception as e:
print(f"✗ Error seed data: {e}")