#!/bin/bash # ============================================================================ # CREATE DATABASE SCRIPT - OrbiQuant IA # ============================================================================ # # Script de carga limpia para crear la base de datos desde DDL. # Cumple con DIRECTIVA-POLITICA-CARGA-LIMPIA.md # # USO: # ./create-database.sh # Crear BD (si no existe) # ./create-database.sh --drop-first # Drop y recrear # ./create-database.sh --seeds-only # Solo ejecutar seeds # # ============================================================================ set -e # Colores para output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuración DB_NAME="${DB_NAME:-orbiquant}" DB_USER="${DB_USER:-orbiquant_user}" DB_PASSWORD="${DB_PASSWORD:-orbiquant_dev_2025}" DB_HOST="${DB_HOST:-localhost}" DB_PORT="${DB_PORT:-5432}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DDL_DIR="$SCRIPT_DIR/../ddl" SEEDS_DIR="$SCRIPT_DIR/../seeds" # Función de logging log() { echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" } log_success() { echo -e "${GREEN}[✓]${NC} $1" } log_warning() { echo -e "${YELLOW}[!]${NC} $1" } log_error() { echo -e "${RED}[✗]${NC} $1" } # Función para ejecutar SQL run_sql() { local file=$1 local description=$2 if [ -f "$file" ]; then log "Ejecutando: $description" PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -f "$file" -q log_success "$description" else log_warning "Archivo no encontrado: $file" fi } # Función para ejecutar SQL en orden run_sql_dir() { local dir=$1 local description=$2 if [ -d "$dir" ]; then log "Procesando directorio: $description" for file in "$dir"/*.sql; do if [ -f "$file" ]; then local filename=$(basename "$file") run_sql "$file" "$filename" fi done else log_warning "Directorio no encontrado: $dir" fi } # Verificar conexión check_connection() { log "Verificando conexión a PostgreSQL..." if PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d postgres -c '\q' 2>/dev/null; then log_success "Conexión exitosa" else log_error "No se puede conectar a PostgreSQL" exit 1 fi } # Drop database si existe drop_database() { log "Eliminando base de datos $DB_NAME si existe..." PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d postgres -c "DROP DATABASE IF EXISTS $DB_NAME;" -q log_success "Base de datos eliminada" } # Crear database create_database() { log "Creando base de datos $DB_NAME..." PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d postgres -c "CREATE DATABASE $DB_NAME WITH ENCODING 'UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8' TEMPLATE=template0;" -q 2>/dev/null || true log_success "Base de datos creada" } # Crear schemas create_schemas() { log "Creando schemas..." PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -q << EOF -- Schemas principales CREATE SCHEMA IF NOT EXISTS auth; CREATE SCHEMA IF NOT EXISTS education; CREATE SCHEMA IF NOT EXISTS trading; CREATE SCHEMA IF NOT EXISTS investment; CREATE SCHEMA IF NOT EXISTS financial; CREATE SCHEMA IF NOT EXISTS ml; CREATE SCHEMA IF NOT EXISTS llm; CREATE SCHEMA IF NOT EXISTS audit; -- Comentarios COMMENT ON SCHEMA auth IS 'Autenticación y usuarios'; COMMENT ON SCHEMA education IS 'Plataforma educativa'; COMMENT ON SCHEMA trading IS 'Trading y paper engine'; COMMENT ON SCHEMA investment IS 'Cuentas PAMM'; COMMENT ON SCHEMA financial IS 'Pagos, suscripciones, wallets'; COMMENT ON SCHEMA ml IS 'Machine Learning signals'; COMMENT ON SCHEMA llm IS 'LLM Agent'; COMMENT ON SCHEMA audit IS 'Auditoría y logs'; -- Search path ALTER DATABASE $DB_NAME SET search_path TO auth, public; EOF log_success "Schemas creados" } # Cargar extensiones load_extensions() { log "Cargando extensiones..." PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -q << EOF CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "pgcrypto"; CREATE EXTENSION IF NOT EXISTS "pg_trgm"; CREATE EXTENSION IF NOT EXISTS "btree_gin"; -- CREATE EXTENSION IF NOT EXISTS "vector"; -- Descomentar cuando se instale pgvector EOF log_success "Extensiones cargadas" } # Cargar DDL por schema load_ddl() { local schema=$1 local schema_dir="$DDL_DIR/schemas/$schema" log "Cargando DDL para schema: $schema" # 1. Enums primero if [ -f "$schema_dir/00-enums.sql" ]; then run_sql "$schema_dir/00-enums.sql" "$schema: ENUMs" fi # 2. Tablas en orden run_sql_dir "$schema_dir/tables" "$schema: Tables" # 3. Funciones run_sql_dir "$schema_dir/functions" "$schema: Functions" # 4. Triggers run_sql_dir "$schema_dir/triggers" "$schema: Triggers" # 5. Views run_sql_dir "$schema_dir/views" "$schema: Views" # 6. Indexes adicionales if [ -f "$schema_dir/99-indexes.sql" ]; then run_sql "$schema_dir/99-indexes.sql" "$schema: Indexes" fi log_success "Schema $schema cargado" } # Cargar todos los DDL load_all_ddl() { log "Cargando todos los DDL..." # Orden de carga (respeta dependencias) local schemas=("auth" "education" "financial" "trading" "investment" "ml" "llm" "audit") for schema in "${schemas[@]}"; do if [ -d "$DDL_DIR/schemas/$schema" ]; then load_ddl "$schema" else log_warning "Schema $schema no tiene DDL definido" fi done log_success "Todos los DDL cargados" } # Cargar seeds load_seeds() { local env=${1:-prod} local seeds_path="$SEEDS_DIR/$env" log "Cargando seeds ($env)..." if [ -d "$seeds_path" ]; then for schema_dir in "$seeds_path"/*/; do if [ -d "$schema_dir" ]; then local schema=$(basename "$schema_dir") run_sql_dir "$schema_dir" "Seeds: $schema" fi done log_success "Seeds cargados" else log_warning "No hay seeds para ambiente: $env" fi } # Validar integridad validate_database() { log "Validando integridad de la base de datos..." # Contar tablas por schema local result=$(PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -t -c " SELECT schemaname, COUNT(*) FROM pg_tables WHERE schemaname IN ('auth', 'education', 'trading', 'investment', 'financial', 'ml', 'llm', 'audit') GROUP BY schemaname ORDER BY schemaname; ") echo "" echo "=== Resumen de Tablas por Schema ===" echo "$result" echo "" # Verificar FKs local fk_count=$(PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -t -c " SELECT COUNT(*) FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'; ") log_success "Foreign Keys: $fk_count" log_success "Validación completada" } # Main main() { echo "" echo "==============================================" echo " OrbiQuant IA - Database Setup" echo " Política de Carga Limpia (DDL-First)" echo "==============================================" echo "" local drop_first=false local seeds_only=false local seed_env="prod" # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --drop-first) drop_first=true shift ;; --seeds-only) seeds_only=true shift ;; --env) seed_env=$2 shift 2 ;; *) log_error "Argumento desconocido: $1" exit 1 ;; esac done check_connection if [ "$seeds_only" = true ]; then load_seeds "$seed_env" exit 0 fi if [ "$drop_first" = true ]; then drop_database fi create_database create_schemas load_extensions load_all_ddl load_seeds "$seed_env" validate_database echo "" log_success "Base de datos creada exitosamente!" echo "" } main "$@"