- Replace old DDL structure with new numbered files (01-24) - Update migrations and seeds for new schema - Clean up deprecated files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
482 lines
14 KiB
Bash
Executable File
482 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
|
# =============================================================
|
|
# SCRIPT: recreate-database.sh
|
|
# DESCRIPCION: Script de recreacion completa de base de datos ERP-Core
|
|
# VERSION: 1.0.0
|
|
# PROYECTO: ERP-Core V2
|
|
# FECHA: 2026-01-10
|
|
# =============================================================
|
|
|
|
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
|
|
|
|
# Configuracion por defecto
|
|
DB_HOST="${DB_HOST:-localhost}"
|
|
DB_PORT="${DB_PORT:-5432}"
|
|
DB_NAME="${DB_NAME:-erp_core}"
|
|
DB_USER="${DB_USER:-postgres}"
|
|
DB_PASSWORD="${DB_PASSWORD:-}"
|
|
|
|
# Directorios
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
DATABASE_DIR="$(dirname "$SCRIPT_DIR")"
|
|
DDL_DIR="$DATABASE_DIR/ddl"
|
|
MIGRATIONS_DIR="$DATABASE_DIR/migrations"
|
|
SEEDS_DIR="$DATABASE_DIR/seeds"
|
|
|
|
# Flags
|
|
DROP_DB=false
|
|
LOAD_SEEDS=false
|
|
VERBOSE=false
|
|
DRY_RUN=false
|
|
|
|
# Funciones de utilidad
|
|
log_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[OK]${NC} $1"
|
|
}
|
|
|
|
log_warning() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
show_help() {
|
|
echo "Uso: $0 [opciones]"
|
|
echo ""
|
|
echo "Script de recreacion de base de datos ERP-Core"
|
|
echo ""
|
|
echo "Opciones:"
|
|
echo " -h, --help Mostrar esta ayuda"
|
|
echo " -d, --drop Eliminar y recrear la base de datos completa"
|
|
echo " -s, --seeds Cargar seeds de desarrollo"
|
|
echo " -v, --verbose Modo verbose"
|
|
echo " --dry-run Mostrar comandos sin ejecutar"
|
|
echo ""
|
|
echo "Variables de entorno:"
|
|
echo " DB_HOST Host de la base de datos (default: localhost)"
|
|
echo " DB_PORT Puerto de la base de datos (default: 5432)"
|
|
echo " DB_NAME Nombre de la base de datos (default: erp_core)"
|
|
echo " DB_USER Usuario de la base de datos (default: postgres)"
|
|
echo " DB_PASSWORD Password de la base de datos"
|
|
echo ""
|
|
echo "Ejemplos:"
|
|
echo " $0 Ejecutar DDL y migraciones sin eliminar DB"
|
|
echo " $0 -d Eliminar y recrear DB completa"
|
|
echo " $0 -d -s Eliminar, recrear DB y cargar seeds"
|
|
echo " DB_HOST=db.example.com $0 -d Usar host remoto"
|
|
}
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-h|--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
-d|--drop)
|
|
DROP_DB=true
|
|
shift
|
|
;;
|
|
-s|--seeds)
|
|
LOAD_SEEDS=true
|
|
shift
|
|
;;
|
|
-v|--verbose)
|
|
VERBOSE=true
|
|
shift
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=true
|
|
shift
|
|
;;
|
|
*)
|
|
log_error "Opcion desconocida: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Construir connection string
|
|
get_psql_cmd() {
|
|
local cmd="psql -h $DB_HOST -p $DB_PORT -U $DB_USER"
|
|
if [ -n "$DB_PASSWORD" ]; then
|
|
cmd="PGPASSWORD=$DB_PASSWORD $cmd"
|
|
fi
|
|
echo "$cmd"
|
|
}
|
|
|
|
run_sql() {
|
|
local db="$1"
|
|
local sql="$2"
|
|
local psql_cmd=$(get_psql_cmd)
|
|
|
|
if [ "$DRY_RUN" = true ]; then
|
|
log_info "[DRY-RUN] Ejecutaria en $db: $sql"
|
|
return 0
|
|
fi
|
|
|
|
if [ "$VERBOSE" = true ]; then
|
|
log_info "Ejecutando: $sql"
|
|
fi
|
|
|
|
eval "$psql_cmd -d $db -c \"$sql\""
|
|
}
|
|
|
|
run_sql_file() {
|
|
local db="$1"
|
|
local file="$2"
|
|
local psql_cmd=$(get_psql_cmd)
|
|
|
|
if [ ! -f "$file" ]; then
|
|
log_error "Archivo no encontrado: $file"
|
|
return 1
|
|
fi
|
|
|
|
if [ "$DRY_RUN" = true ]; then
|
|
log_info "[DRY-RUN] Ejecutaria archivo: $file"
|
|
return 0
|
|
fi
|
|
|
|
if [ "$VERBOSE" = true ]; then
|
|
log_info "Ejecutando archivo: $file"
|
|
fi
|
|
|
|
eval "$psql_cmd -d $db -f \"$file\""
|
|
}
|
|
|
|
# Drop y recrear base de datos
|
|
drop_and_create_db() {
|
|
local psql_cmd=$(get_psql_cmd)
|
|
|
|
log_info "Eliminando base de datos existente: $DB_NAME"
|
|
|
|
if [ "$DRY_RUN" = true ]; then
|
|
log_info "[DRY-RUN] DROP DATABASE IF EXISTS $DB_NAME"
|
|
log_info "[DRY-RUN] CREATE DATABASE $DB_NAME"
|
|
return 0
|
|
fi
|
|
|
|
# Terminar conexiones activas
|
|
eval "$psql_cmd -d postgres -c \"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$DB_NAME' AND pid <> pg_backend_pid();\"" 2>/dev/null || true
|
|
|
|
# Eliminar y crear base de datos
|
|
eval "$psql_cmd -d postgres -c \"DROP DATABASE IF EXISTS $DB_NAME;\""
|
|
eval "$psql_cmd -d postgres -c \"CREATE DATABASE $DB_NAME WITH ENCODING 'UTF8' LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0;\""
|
|
|
|
log_success "Base de datos $DB_NAME creada"
|
|
}
|
|
|
|
# Crear schemas base
|
|
create_base_schemas() {
|
|
log_info "Creando schemas base..."
|
|
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS auth;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS core;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS mobile;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS billing;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS users;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS flags;"
|
|
# Sprint 3+ schemas
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS notifications;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS audit;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS webhooks;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS storage;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS ai;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS whatsapp;"
|
|
# Business modules schemas
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS partners;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS products;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS inventory;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS sales;"
|
|
run_sql "$DB_NAME" "CREATE SCHEMA IF NOT EXISTS purchases;"
|
|
|
|
log_success "Schemas base creados"
|
|
}
|
|
|
|
# Crear extensiones requeridas
|
|
create_extensions() {
|
|
log_info "Creando extensiones requeridas..."
|
|
|
|
run_sql "$DB_NAME" "CREATE EXTENSION IF NOT EXISTS cube;"
|
|
run_sql "$DB_NAME" "CREATE EXTENSION IF NOT EXISTS earthdistance;"
|
|
|
|
log_success "Extensiones creadas"
|
|
}
|
|
|
|
# Verificar si existen tablas base
|
|
check_base_tables() {
|
|
local psql_cmd=$(get_psql_cmd)
|
|
local result
|
|
|
|
result=$(eval "$psql_cmd -d $DB_NAME -t -c \"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'auth' AND table_name IN ('tenants', 'users');\"" 2>/dev/null | tr -d ' ')
|
|
|
|
if [ "$result" -eq "2" ]; then
|
|
return 0 # Tablas existen
|
|
else
|
|
return 1 # Tablas no existen
|
|
fi
|
|
}
|
|
|
|
# Crear tablas base (si no existen y es una recreacion)
|
|
create_base_tables() {
|
|
log_info "Creando tablas base (auth.tenants, auth.users)..."
|
|
|
|
# Solo crear si estamos en modo drop o si no existen
|
|
run_sql "$DB_NAME" "
|
|
CREATE TABLE IF NOT EXISTS auth.tenants (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name VARCHAR(200) NOT NULL,
|
|
slug VARCHAR(100) UNIQUE NOT NULL,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
deleted_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS auth.users (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
|
email VARCHAR(255) NOT NULL,
|
|
password_hash TEXT,
|
|
first_name VARCHAR(100),
|
|
last_name VARCHAR(100),
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
email_verified BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
deleted_at TIMESTAMPTZ,
|
|
UNIQUE(tenant_id, email)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_users_tenant ON auth.users(tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_users_email ON auth.users(email);
|
|
"
|
|
|
|
log_success "Tablas base creadas"
|
|
}
|
|
|
|
# Ejecutar archivos DDL en orden
|
|
run_ddl_files() {
|
|
log_info "Ejecutando archivos DDL..."
|
|
|
|
# Orden especifico de ejecucion
|
|
# Nota: El orden es importante por las dependencias entre tablas
|
|
local ddl_files=(
|
|
# Core existente
|
|
"01-auth-profiles.sql"
|
|
"02-auth-devices.sql"
|
|
"03-core-branches.sql"
|
|
"04-mobile.sql"
|
|
"05-billing-usage.sql"
|
|
# SaaS Extensions - Sprint 1-2 (EPIC-SAAS-001, EPIC-SAAS-002)
|
|
"06-auth-extended.sql"
|
|
"07-users-rbac.sql"
|
|
"08-plans.sql"
|
|
"11-feature-flags.sql"
|
|
# SaaS Extensions - Sprint 3+ (EPIC-SAAS-003 - EPIC-SAAS-008)
|
|
"09-notifications.sql"
|
|
"10-audit.sql"
|
|
"12-webhooks.sql"
|
|
"13-storage.sql"
|
|
"14-ai.sql"
|
|
"15-whatsapp.sql"
|
|
# Business Modules - ERP Core
|
|
"16-partners.sql"
|
|
"17-products.sql"
|
|
"18-warehouses.sql"
|
|
"21-inventory.sql"
|
|
"22-sales.sql"
|
|
"23-purchases.sql"
|
|
"24-invoices.sql"
|
|
)
|
|
|
|
for ddl_file in "${ddl_files[@]}"; do
|
|
local file_path="$DDL_DIR/$ddl_file"
|
|
if [ -f "$file_path" ]; then
|
|
log_info "Ejecutando: $ddl_file"
|
|
run_sql_file "$DB_NAME" "$file_path"
|
|
log_success "Completado: $ddl_file"
|
|
else
|
|
log_warning "Archivo no encontrado: $ddl_file"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Ejecutar migraciones
|
|
run_migrations() {
|
|
log_info "Ejecutando migraciones..."
|
|
|
|
if [ ! -d "$MIGRATIONS_DIR" ]; then
|
|
log_warning "Directorio de migraciones no encontrado: $MIGRATIONS_DIR"
|
|
return 0
|
|
fi
|
|
|
|
# Ordenar migraciones por nombre (fecha)
|
|
local migration_files=$(ls -1 "$MIGRATIONS_DIR"/*.sql 2>/dev/null | sort)
|
|
|
|
if [ -z "$migration_files" ]; then
|
|
log_info "No hay migraciones pendientes"
|
|
return 0
|
|
fi
|
|
|
|
for migration_file in $migration_files; do
|
|
local filename=$(basename "$migration_file")
|
|
log_info "Ejecutando migracion: $filename"
|
|
run_sql_file "$DB_NAME" "$migration_file"
|
|
log_success "Migracion completada: $filename"
|
|
done
|
|
}
|
|
|
|
# Cargar seeds de desarrollo
|
|
load_seeds() {
|
|
log_info "Cargando seeds de desarrollo..."
|
|
|
|
local seeds_dev_dir="$SEEDS_DIR/dev"
|
|
|
|
if [ ! -d "$seeds_dev_dir" ]; then
|
|
log_warning "Directorio de seeds no encontrado: $seeds_dev_dir"
|
|
return 0
|
|
fi
|
|
|
|
# Ordenar seeds por nombre
|
|
local seed_files=$(ls -1 "$seeds_dev_dir"/*.sql 2>/dev/null | sort)
|
|
|
|
if [ -z "$seed_files" ]; then
|
|
log_info "No hay seeds para cargar"
|
|
return 0
|
|
fi
|
|
|
|
for seed_file in $seed_files; do
|
|
local filename=$(basename "$seed_file")
|
|
log_info "Cargando seed: $filename"
|
|
run_sql_file "$DB_NAME" "$seed_file"
|
|
log_success "Seed cargado: $filename"
|
|
done
|
|
}
|
|
|
|
# Validar creacion de tablas
|
|
validate_database() {
|
|
log_info "Validando base de datos..."
|
|
|
|
local psql_cmd=$(get_psql_cmd)
|
|
|
|
if [ "$DRY_RUN" = true ]; then
|
|
log_info "[DRY-RUN] Validaria tablas creadas"
|
|
return 0
|
|
fi
|
|
|
|
# Contar tablas por schema
|
|
echo ""
|
|
echo "=== Resumen de tablas por schema ==="
|
|
echo ""
|
|
|
|
local schemas=("auth" "core" "mobile" "billing" "users" "flags" "notifications" "audit" "webhooks" "storage" "ai" "whatsapp" "partners" "products" "inventory" "sales" "purchases")
|
|
local total_tables=0
|
|
|
|
for schema in "${schemas[@]}"; do
|
|
local count=$(eval "$psql_cmd -d $DB_NAME -t -c \"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '$schema';\"" | tr -d ' ')
|
|
echo " $schema: $count tablas"
|
|
total_tables=$((total_tables + count))
|
|
done
|
|
|
|
echo ""
|
|
echo " Total: $total_tables tablas"
|
|
echo ""
|
|
|
|
# Listar tablas principales
|
|
echo "=== Tablas principales creadas ==="
|
|
echo ""
|
|
eval "$psql_cmd -d $DB_NAME -c \"
|
|
SELECT table_schema, table_name
|
|
FROM information_schema.tables
|
|
WHERE table_schema IN ('auth', 'core', 'mobile', 'billing', 'users', 'flags', 'notifications', 'audit', 'webhooks', 'storage', 'ai', 'whatsapp', 'partners', 'products', 'inventory', 'sales', 'purchases')
|
|
AND table_type = 'BASE TABLE'
|
|
ORDER BY table_schema, table_name;
|
|
\""
|
|
|
|
log_success "Validacion completada"
|
|
}
|
|
|
|
# Mostrar configuracion actual
|
|
show_config() {
|
|
echo ""
|
|
echo "=== Configuracion ==="
|
|
echo " Host: $DB_HOST"
|
|
echo " Puerto: $DB_PORT"
|
|
echo " Base de datos: $DB_NAME"
|
|
echo " Usuario: $DB_USER"
|
|
echo " Drop DB: $DROP_DB"
|
|
echo " Cargar seeds: $LOAD_SEEDS"
|
|
echo " Dry run: $DRY_RUN"
|
|
echo ""
|
|
}
|
|
|
|
# Main
|
|
main() {
|
|
parse_args "$@"
|
|
|
|
echo "=================================================="
|
|
echo " ERP-Core Database Recreation Script v1.0.0"
|
|
echo "=================================================="
|
|
|
|
show_config
|
|
|
|
# Verificar que psql esta disponible
|
|
if ! command -v psql &> /dev/null; then
|
|
log_error "psql no encontrado. Instalar PostgreSQL client."
|
|
exit 1
|
|
fi
|
|
|
|
# Verificar conexion
|
|
log_info "Verificando conexion a PostgreSQL..."
|
|
local psql_cmd=$(get_psql_cmd)
|
|
if ! eval "$psql_cmd -d postgres -c 'SELECT 1;'" &> /dev/null; then
|
|
log_error "No se puede conectar a PostgreSQL. Verificar credenciales."
|
|
exit 1
|
|
fi
|
|
log_success "Conexion exitosa"
|
|
|
|
# Ejecutar pasos
|
|
if [ "$DROP_DB" = true ]; then
|
|
drop_and_create_db
|
|
create_base_schemas
|
|
create_extensions
|
|
create_base_tables
|
|
fi
|
|
|
|
# Ejecutar DDL
|
|
run_ddl_files
|
|
|
|
# Ejecutar migraciones
|
|
run_migrations
|
|
|
|
# Cargar seeds si se solicito
|
|
if [ "$LOAD_SEEDS" = true ]; then
|
|
load_seeds
|
|
fi
|
|
|
|
# Validar
|
|
validate_database
|
|
|
|
echo ""
|
|
log_success "Recreacion de base de datos completada exitosamente"
|
|
echo ""
|
|
}
|
|
|
|
main "$@"
|