erp-core-database-v2/scripts/recreate-database.sh
Adrian Flores Cortes 02ab2caf26 [TASK-2026-02-05-EJECUCION-REMEDIATION-ERP-CORE] feat: DDL fixes and new schemas
- 00-auth-base.sql: Extracted auth.tenants+users from recreate-database.sh
- 03b-core-companies.sql: DDL for auth.companies entity
- 21b-inventory-extended.sql: 7 new tables for inventory entities without DDL
- 24-invoices.sql: billing→operations schema to resolve duplication
- 27/28/29-cfdi: Track existing CFDI DDL files
- recreate-database.sh: Updated ddl_files array (17→43 entries)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 21:52:22 -06:00

512 lines
15 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=(
# Auth base (tenants + users DDL - también creadas inline como safety net)
"00-auth-base.sql"
# 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"
"07a-permissions-seed.sql"
"08-plans.sql"
# SaaS Extensions - Sprint 3+ (EPIC-SAAS-003 - EPIC-SAAS-008)
"09-notifications.sql"
"10-audit.sql"
"11-feature-flags.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"
"19-product-attributes.sql"
"20-core-catalogs.sql"
"21-inventory.sql"
"22-sales.sql"
"23-purchases.sql"
"24-invoices.sql"
"25-payment-terminals.sql"
# Fiscal / CFDI
"26-fiscal-catalogs.sql"
"27-cfdi-core.sql"
"28-cfdi-operations.sql"
"29-cfdi-rls-functions.sql"
# Settings & Reports
"30-settings.sql"
"31-reports.sql"
# HR & Purchases Extended
"45-hr.sql"
"46-purchases-matching.sql"
# Financial Module
"50-financial-schema.sql"
"51-financial-accounts.sql"
"52-financial-journals.sql"
"53-financial-entries.sql"
"54-financial-invoices.sql"
"55-financial-payments.sql"
"56-financial-taxes.sql"
"57-financial-bank-reconciliation.sql"
# Projects
"60-projects-timesheets.sql"
# RLS (must be last - depends on all tables)
"99-rls-erp-modules.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 "$@"