erp-construccion-database-v2/drop-and-recreate-database.sh
Adrian Flores Cortes 5f009673aa [REMEDIATION] feat: Database schema updates and recreate script enhancement
Major overhaul of drop-and-recreate-database.sh, DDL schema updates,
seed data cleanup. Add utility scripts for auth table fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 23:18:19 -06:00

903 lines
30 KiB
Bash
Executable File

#!/bin/bash
# =============================================================================
# DROP AND RECREATE DATABASE - ERP CONSTRUCCION
# =============================================================================
# Script de carga limpia con soporte para seeds por ambiente
#
# Uso: ./drop-and-recreate-database.sh [opciones] [DATABASE_URL]
#
# Opciones:
# -s, --seeds Cargar seeds despues de DDL
# -e, --env ENV Ambiente: dev|staging|prod (default: dev)
# -f, --force No pedir confirmacion
# -v, --verbose Modo verbose
# -h, --help Mostrar ayuda
#
# Ejemplos:
# ./drop-and-recreate-database.sh --seeds
# ./drop-and-recreate-database.sh --seeds --env dev
# ./drop-and-recreate-database.sh -s -f "postgresql://user:pass@localhost:5432/mydb"
#
# Referencia: @SIMCO-DDL-UNIFIED, @TRIGGER-DDL-WSL
# =============================================================================
set -e
# =============================================================================
# CONFIGURACION
# =============================================================================
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
# Directorios
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCHEMAS_DIR="$SCRIPT_DIR/schemas"
INIT_SCRIPTS_DIR="$SCRIPT_DIR/init-scripts"
SEEDS_DIR="$SCRIPT_DIR/seeds"
# Valores por defecto
LOAD_SEEDS=false
ENVIRONMENT="dev"
FORCE=false
VERBOSE=false
DATABASE_URL=""
# Credenciales por defecto (segun WORKSPACE-INTEGRATION.yml)
DEFAULT_HOST="localhost"
DEFAULT_PORT="5432"
DEFAULT_DB="erp_construccion_demo"
DEFAULT_USER="erp_admin"
DEFAULT_PASS="erp_dev_2026"
# =============================================================================
# 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"; }
log_step() { echo -e "${CYAN}[STEP]${NC} $1"; }
show_help() {
cat << EOF
Uso: $0 [opciones] [DATABASE_URL]
Script de recreacion de base de datos con soporte para seeds.
Opciones:
-s, --seeds Cargar seeds despues del DDL
-e, --env ENV Ambiente para seeds: dev|staging|prod (default: dev)
-f, --force No pedir confirmacion para DROP
-v, --verbose Mostrar output detallado
-h, --help Mostrar esta ayuda
DATABASE_URL:
Formato: postgresql://user:pass@host:port/database
Default: postgresql://$DEFAULT_USER:****@$DEFAULT_HOST:$DEFAULT_PORT/$DEFAULT_DB
Ejemplos:
$0 --seeds # Recrear BD y cargar seeds dev
$0 --seeds --env staging # Recrear BD con seeds staging
$0 -s -f # Recrear sin confirmacion
$0 "postgresql://..." # Usar URL personalizada
Seeds disponibles por ambiente:
dev/ - Datos completos para desarrollo (usuarios demo, proyectos, etc.)
staging/ - Datos minimos para pruebas
prod/ - Solo catalogos base (sin datos de prueba)
EOF
}
# =============================================================================
# PARSEO DE ARGUMENTOS
# =============================================================================
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-s|--seeds)
LOAD_SEEDS=true
shift
;;
-e|--env)
ENVIRONMENT="$2"
shift 2
;;
-f|--force)
FORCE=true
shift
;;
-v|--verbose)
VERBOSE=true
shift
;;
postgresql://*)
DATABASE_URL="$1"
shift
;;
*)
log_error "Opcion desconocida: $1"
show_help
exit 1
;;
esac
done
# Construir DATABASE_URL si no se proporciono
if [ -z "$DATABASE_URL" ]; then
DATABASE_URL="postgresql://$DEFAULT_USER:$DEFAULT_PASS@$DEFAULT_HOST:$DEFAULT_PORT/$DEFAULT_DB"
fi
# Validar ambiente
if [[ ! "$ENVIRONMENT" =~ ^(dev|staging|prod)$ ]]; then
log_error "Ambiente invalido: $ENVIRONMENT (usar: dev|staging|prod)"
exit 1
fi
}
# =============================================================================
# FUNCIONES DE BASE DE DATOS
# =============================================================================
run_sql() {
local sql="$1"
if [ "$VERBOSE" = true ]; then
psql "$DATABASE_URL" -c "$sql"
else
psql "$DATABASE_URL" -c "$sql" > /dev/null 2>&1
fi
}
run_sql_file() {
local file="$1"
if [ "$VERBOSE" = true ]; then
psql "$DATABASE_URL" -f "$file"
else
psql "$DATABASE_URL" -f "$file" > /dev/null 2>&1
fi
}
run_sql_quiet() {
local sql="$1"
psql "$DATABASE_URL" -t -A -c "$sql" 2>/dev/null | tr -d ' \n'
}
# =============================================================================
# PASO 0: VERIFICACIONES INICIALES
# =============================================================================
verify_connection() {
log_step "Verificando conexion a PostgreSQL..."
if ! psql "$DATABASE_URL" -c "SELECT 1" > /dev/null 2>&1; then
log_error "No se puede conectar a PostgreSQL"
log_info "URL: $DATABASE_URL"
log_info "Verificar que PostgreSQL esta corriendo y las credenciales son correctas"
exit 1
fi
log_success "Conexion establecida"
}
confirm_drop() {
if [ "$FORCE" = true ]; then
return 0
fi
local db_name=$(echo "$DATABASE_URL" | sed -E 's|.*/([^?]+).*|\1|')
echo ""
echo -e "${RED}ADVERTENCIA: Esto eliminara TODOS los datos de: $db_name${NC}"
echo ""
read -p "Escribe 'SI' para confirmar: " confirm
if [ "$confirm" != "SI" ]; then
log_info "Operacion cancelada"
exit 0
fi
}
# =============================================================================
# PASO 1: DROP SCHEMAS EXISTENTES
# =============================================================================
drop_schemas() {
log_step "Eliminando schemas existentes..."
# Schemas a eliminar (orden inverso de dependencias)
local schemas=(
"documents" "assets" "finance" "infonavit" "hse" "hr"
"purchase" "inventory" "estimates" "construction"
"tenants" "core_shared" "core" "auth"
# Legacy schemas
"infonavit_management" "safety_management" "quality_management"
"construction_management" "inventory_management" "purchasing_management"
"financial_management" "project_management" "auth_management"
)
for schema in "${schemas[@]}"; do
if [ "$(run_sql_quiet "SELECT 1 FROM pg_namespace WHERE nspname = '$schema'")" = "1" ]; then
run_sql "DROP SCHEMA IF EXISTS $schema CASCADE"
[ "$VERBOSE" = true ] && log_info "Schema $schema eliminado"
fi
done
log_success "Schemas eliminados"
}
# =============================================================================
# PASO 2: CREAR SCHEMAS BASE (auth, core)
# =============================================================================
create_base_schemas() {
log_step "Creando schemas base (auth, core)..."
# auth y core deben existir antes de ejecutar los DDLs
psql "$DATABASE_URL" << 'EOSQL' > /dev/null 2>&1
-- Crear schemas base que son prerequisito
CREATE SCHEMA IF NOT EXISTS auth;
CREATE SCHEMA IF NOT EXISTS core;
CREATE SCHEMA IF NOT EXISTS core_shared;
CREATE SCHEMA IF NOT EXISTS tenants;
-- Tabla auth.tenants (necesaria para FK)
CREATE TABLE IF NOT EXISTS auth.tenants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
settings JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Tabla auth.users (necesaria para FK)
CREATE TABLE IF NOT EXISTS auth.users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES auth.tenants(id),
email VARCHAR(255) NOT NULL,
username VARCHAR(100),
is_active BOOLEAN DEFAULT true,
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
COMMENT ON SCHEMA auth IS 'Schema de autenticacion - tablas base para FK';
COMMENT ON SCHEMA core IS 'Schema de catalogos core';
EOSQL
log_success "Schemas base creados"
}
# =============================================================================
# PASO 3: EJECUTAR DDL INICIAL
# =============================================================================
execute_init_ddl() {
log_step "Ejecutando DDL inicial..."
if [ -f "$INIT_SCRIPTS_DIR/01-init-database.sql" ]; then
run_sql_file "$INIT_SCRIPTS_DIR/01-init-database.sql"
log_success "01-init-database.sql ejecutado"
else
log_error "No se encontro archivo de inicializacion"
exit 1
fi
}
# =============================================================================
# PASO 4: EJECUTAR DDL DE SCHEMAS
# =============================================================================
execute_schema_ddl() {
log_step "Ejecutando DDL de schemas..."
if [ ! -d "$SCHEMAS_DIR" ]; then
log_warning "Directorio schemas/ no encontrado"
return 0
fi
# Ejecutar en orden numerico
for ddl_file in $(ls -1 "$SCHEMAS_DIR"/*.sql 2>/dev/null | sort -V); do
local filename=$(basename "$ddl_file")
echo -ne " - ${YELLOW}$filename${NC}..."
if run_sql_file "$ddl_file"; then
echo -e " ${GREEN}OK${NC}"
else
echo -e " ${RED}ERROR${NC}"
log_error "Fallo al ejecutar: $ddl_file"
exit 1
fi
done
log_success "DDL de schemas completado"
}
# =============================================================================
# PASO 4: CREAR TABLAS COMPLEMENTARIAS (para seeds)
# =============================================================================
create_complementary_tables() {
log_step "Creando tablas complementarias de erp-core..."
# Estas tablas son necesarias para que los seeds funcionen
# cuando se ejecuta standalone (sin erp-core completo)
psql "$DATABASE_URL" << 'EOSQL' > /dev/null 2>&1
-- ============================================================
-- TABLAS COMPLEMENTARIAS PARA SEEDS
-- Estas tablas normalmente vienen de erp-core
-- ============================================================
-- Schema tenants
CREATE SCHEMA IF NOT EXISTS tenants;
-- tenants.tenants (estructura completa)
CREATE TABLE IF NOT EXISTS tenants.tenants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
legal_name VARCHAR(255),
rfc VARCHAR(20),
business_type VARCHAR(100),
status VARCHAR(20) DEFAULT 'active',
address_street TEXT,
address_city VARCHAR(100),
address_state VARCHAR(100),
address_zip VARCHAR(20),
address_country VARCHAR(100) DEFAULT 'Mexico',
phone VARCHAR(50),
email VARCHAR(255),
website VARCHAR(255),
logo_url VARCHAR(500),
primary_color VARCHAR(10),
secondary_color VARCHAR(10),
fiscal_regime VARCHAR(10),
cfdi_certificate_number VARCHAR(50),
settings JSONB DEFAULT '{}',
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID,
updated_at TIMESTAMPTZ DEFAULT NOW(),
updated_by UUID
);
-- tenants.tenant_settings
CREATE TABLE IF NOT EXISTS tenants.tenant_settings (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants.tenants(id),
setting_key VARCHAR(100) NOT NULL,
setting_value JSONB,
setting_type VARCHAR(50),
description TEXT,
is_system BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- tenants.branches
CREATE TABLE IF NOT EXISTS tenants.branches (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants.tenants(id),
code VARCHAR(50) NOT NULL,
name VARCHAR(200) NOT NULL,
address TEXT,
city VARCHAR(100),
state VARCHAR(100),
phone VARCHAR(50),
is_main BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID,
UNIQUE(tenant_id, code)
);
-- auth.tenants (si no existe)
CREATE TABLE IF NOT EXISTS auth.tenants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
settings JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- auth.users (extender si existe)
DO $$ BEGIN
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS password_hash VARCHAR(255);
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS first_name VARCHAR(100);
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS last_name VARCHAR(100);
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS middle_name VARCHAR(100);
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS phone VARCHAR(50);
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS avatar_url VARCHAR(500);
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS position VARCHAR(100);
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS department VARCHAR(100);
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS role_id UUID;
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS language VARCHAR(10) DEFAULT 'es';
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS timezone VARCHAR(50) DEFAULT 'America/Mexico_City';
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMPTZ;
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS email_verified BOOLEAN DEFAULT false;
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS is_verified BOOLEAN DEFAULT false;
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS is_system BOOLEAN DEFAULT false;
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS failed_login_attempts INT DEFAULT 0;
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS locked_until TIMESTAMPTZ;
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS must_change_password BOOLEAN DEFAULT false;
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS created_by UUID;
ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS settings JSONB DEFAULT '{}';
EXCEPTION WHEN undefined_table THEN NULL; END $$;
-- auth.roles
CREATE TABLE IF NOT EXISTS auth.roles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID,
code VARCHAR(50) NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
permissions JSONB DEFAULT '[]',
is_system BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, code)
);
-- auth.permissions
CREATE TABLE IF NOT EXISTS auth.permissions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID,
code VARCHAR(100) NOT NULL,
name VARCHAR(200) NOT NULL,
description TEXT,
module VARCHAR(50),
action VARCHAR(50),
resource VARCHAR(100),
is_system BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, code)
);
-- auth.user_roles
CREATE TABLE IF NOT EXISTS auth.user_roles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID,
role_id UUID,
tenant_id UUID,
is_primary BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, role_id)
);
-- auth.role_permissions
CREATE TABLE IF NOT EXISTS auth.role_permissions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
role_id UUID,
permission_id UUID,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(role_id, permission_id)
);
-- core.units (estructura completa)
DROP TABLE IF EXISTS core.units CASCADE;
CREATE TABLE core.units (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID,
code VARCHAR(20) NOT NULL,
name VARCHAR(100) NOT NULL,
symbol VARCHAR(10),
abbreviation VARCHAR(10),
category VARCHAR(50),
unit_type VARCHAR(50),
description TEXT,
base_unit_id UUID,
conversion_factor DECIMAL(18,6) DEFAULT 1,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID,
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(code)
);
-- core.currencies
CREATE TABLE IF NOT EXISTS core.currencies (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(3) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
symbol VARCHAR(10),
decimal_places INT DEFAULT 2,
is_active BOOLEAN DEFAULT true,
is_default BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- core.taxes
CREATE TABLE IF NOT EXISTS core.taxes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(20) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
rate DECIMAL(5,4) NOT NULL,
tax_type VARCHAR(20),
is_retention BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- core.payment_methods
CREATE TABLE IF NOT EXISTS core.payment_methods (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(20) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
description TEXT,
requires_reference BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- core.banks
CREATE TABLE IF NOT EXISTS core.banks (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(20) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL,
short_name VARCHAR(50),
swift_code VARCHAR(20),
country VARCHAR(50) DEFAULT 'Mexico',
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- core.states
CREATE TABLE IF NOT EXISTS core.states (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(10) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
abbreviation VARCHAR(10),
country VARCHAR(50) DEFAULT 'Mexico',
country_code VARCHAR(3) DEFAULT 'MX',
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- core.municipalities
CREATE TABLE IF NOT EXISTS core.municipalities (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
state_id UUID REFERENCES core.states(id),
code VARCHAR(10),
name VARCHAR(200),
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- core.material_categories
CREATE TABLE IF NOT EXISTS core.material_categories (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL,
parent_id UUID,
description TEXT,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- core.cost_types
CREATE TABLE IF NOT EXISTS core.cost_types (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(20) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
description TEXT,
factor DECIMAL(5,2),
is_direct BOOLEAN DEFAULT true,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- core.document_types
CREATE TABLE IF NOT EXISTS core.document_types (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(20) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
description TEXT,
category VARCHAR(50),
requires_signature BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Columnas adicionales para tablas existentes
DO $$ BEGIN
-- construction.fraccionamientos
ALTER TABLE construction.fraccionamientos ADD COLUMN IF NOT EXISTS project_type VARCHAR(50);
ALTER TABLE construction.fraccionamientos ADD COLUMN IF NOT EXISTS estimated_end_date DATE;
ALTER TABLE construction.fraccionamientos ADD COLUMN IF NOT EXISTS budget DECIMAL(18,2);
ALTER TABLE construction.fraccionamientos ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT true;
-- construction.conceptos
ALTER TABLE construction.conceptos ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT true;
-- hse.incidentes
ALTER TABLE hse.incidentes ADD COLUMN IF NOT EXISTS acciones_inmediatas TEXT;
ALTER TABLE hse.incidentes ADD COLUMN IF NOT EXISTS investigado_por UUID;
ALTER TABLE hse.incidentes ADD COLUMN IF NOT EXISTS fecha_investigacion DATE;
ALTER TABLE hse.incidentes ADD COLUMN IF NOT EXISTS cerrado_por UUID;
ALTER TABLE hse.incidentes ADD COLUMN IF NOT EXISTS fecha_cierre DATE;
-- hse.capacitaciones
ALTER TABLE hse.capacitaciones ADD COLUMN IF NOT EXISTS puntaje_minimo INTEGER;
-- hse.horas_trabajadas
ALTER TABLE hse.horas_trabajadas ADD COLUMN IF NOT EXISTS trabajadores_directos INTEGER;
ALTER TABLE hse.horas_trabajadas ADD COLUMN IF NOT EXISTS trabajadores_indirectos INTEGER;
-- assets.assets
ALTER TABLE assets.assets ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT true;
-- assets.maintenance_plans
ALTER TABLE assets.maintenance_plans ADD COLUMN IF NOT EXISTS estimated_cost DECIMAL(18,2);
ALTER TABLE assets.maintenance_plans ADD COLUMN IF NOT EXISTS actual_cost DECIMAL(18,2);
ALTER TABLE assets.maintenance_plans ADD COLUMN IF NOT EXISTS tasks_description TEXT;
ALTER TABLE assets.maintenance_plans ADD COLUMN IF NOT EXISTS last_performed_at TIMESTAMPTZ;
ALTER TABLE assets.maintenance_plans ADD COLUMN IF NOT EXISTS next_due_at TIMESTAMPTZ;
ALTER TABLE assets.maintenance_plans ALTER COLUMN activities DROP NOT NULL;
EXCEPTION WHEN undefined_table THEN NULL; END $$;
EOSQL
log_success "Tablas complementarias creadas"
}
# =============================================================================
# PASO 5: OTORGAR PERMISOS
# =============================================================================
grant_permissions() {
log_step "Otorgando permisos..."
local db_user=$(echo "$DATABASE_URL" | sed -E 's|.*://([^:]+):.*|\1|')
psql "$DATABASE_URL" << EOSQL > /dev/null 2>&1
-- Permisos sobre schemas
GRANT ALL ON SCHEMA auth TO $db_user;
GRANT ALL ON SCHEMA core TO $db_user;
GRANT ALL ON SCHEMA core_shared TO $db_user;
GRANT ALL ON SCHEMA tenants TO $db_user;
GRANT ALL ON SCHEMA construction TO $db_user;
GRANT ALL ON SCHEMA estimates TO $db_user;
GRANT ALL ON SCHEMA infonavit TO $db_user;
GRANT ALL ON SCHEMA hse TO $db_user;
GRANT ALL ON SCHEMA hr TO $db_user;
GRANT ALL ON SCHEMA inventory TO $db_user;
GRANT ALL ON SCHEMA purchase TO $db_user;
GRANT ALL ON SCHEMA finance TO $db_user;
GRANT ALL ON SCHEMA assets TO $db_user;
GRANT ALL ON SCHEMA documents TO $db_user;
-- Permisos sobre tablas
GRANT ALL ON ALL TABLES IN SCHEMA auth TO $db_user;
GRANT ALL ON ALL TABLES IN SCHEMA core TO $db_user;
GRANT ALL ON ALL TABLES IN SCHEMA core_shared TO $db_user;
GRANT ALL ON ALL TABLES IN SCHEMA tenants TO $db_user;
GRANT ALL ON ALL TABLES IN SCHEMA construction TO $db_user;
GRANT ALL ON ALL TABLES IN SCHEMA estimates TO $db_user;
GRANT ALL ON ALL TABLES IN SCHEMA infonavit TO $db_user;
GRANT ALL ON ALL TABLES IN SCHEMA hse TO $db_user;
GRANT ALL ON ALL TABLES IN SCHEMA hr TO $db_user;
GRANT ALL ON ALL TABLES IN SCHEMA inventory TO $db_user;
GRANT ALL ON ALL TABLES IN SCHEMA purchase TO $db_user;
GRANT ALL ON ALL TABLES IN SCHEMA finance TO $db_user;
GRANT ALL ON ALL TABLES IN SCHEMA assets TO $db_user;
GRANT ALL ON ALL TABLES IN SCHEMA documents TO $db_user;
-- Permisos sobre secuencias
GRANT ALL ON ALL SEQUENCES IN SCHEMA auth TO $db_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA core TO $db_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA construction TO $db_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA estimates TO $db_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA infonavit TO $db_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA hse TO $db_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA hr TO $db_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA inventory TO $db_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA purchase TO $db_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA finance TO $db_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA assets TO $db_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA documents TO $db_user;
-- Permisos por defecto
ALTER DEFAULT PRIVILEGES IN SCHEMA auth GRANT ALL ON TABLES TO $db_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA core GRANT ALL ON TABLES TO $db_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA construction GRANT ALL ON TABLES TO $db_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA estimates GRANT ALL ON TABLES TO $db_user;
EOSQL
log_success "Permisos otorgados"
}
# =============================================================================
# PASO 6: CARGAR SEEDS
# =============================================================================
load_seeds() {
local env_dir="$SEEDS_DIR/$ENVIRONMENT"
if [ ! -d "$env_dir" ]; then
log_warning "Directorio de seeds no encontrado: $env_dir"
return 0
fi
log_step "Cargando seeds ($ENVIRONMENT)..."
# Seeds en orden de dependencias (las tablas complementarias ya existen)
local seeds=(
"01-core-catalogs.sql"
"02-users-profiles.sql"
"03-empresa-tenant.sql"
"04-proyectos-obras.sql"
"02b-hr-employees.sql"
"05-presupuestos.sql"
"06-estimaciones.sql"
"07-avances-calidad.sql"
"08-hse.sql"
"09-finanzas.sql"
"10-activos-documentos.sql"
"11-infonavit.sql"
)
local success=0
local failed=0
local failed_seeds=""
for seed in "${seeds[@]}"; do
local seed_file="$env_dir/$seed"
if [ ! -f "$seed_file" ]; then
[ "$VERBOSE" = true ] && log_warning "Seed no encontrado: $seed"
continue
fi
echo -ne " - ${YELLOW}$seed${NC}..."
# Ejecutar seed (las transacciones estan dentro de cada archivo)
local output=$(psql "$DATABASE_URL" -f "$seed_file" 2>&1)
if echo "$output" | grep -qE "(^COMMIT|^INSERT|^DO)"; then
echo -e " ${GREEN}OK${NC}"
success=$((success + 1))
else
echo -e " ${RED}FAIL${NC}"
failed=$((failed + 1))
failed_seeds="$failed_seeds $seed"
if [ "$VERBOSE" = true ]; then
echo "$output" | grep -E "(^ERROR|violates)" | head -3
fi
fi
done
echo ""
if [ $failed -eq 0 ]; then
log_success "Todos los seeds cargados ($success/$success)"
else
log_warning "Seeds cargados: $success exitosos, $failed fallidos"
[ "$VERBOSE" = true ] && log_info "Fallidos:$failed_seeds"
fi
}
# =============================================================================
# PASO 7: VERIFICAR RESULTADO
# =============================================================================
verify_result() {
log_step "Verificando resultado..."
local schema_count=$(run_sql_quiet "
SELECT COUNT(*) FROM pg_namespace
WHERE nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast', 'public')
AND nspname NOT LIKE 'pg_%'
")
local table_count=$(run_sql_quiet "
SELECT COUNT(*) FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'public')
")
echo ""
echo -e "${GREEN}=== RESUMEN ===${NC}"
psql "$DATABASE_URL" -c "
SELECT schemaname AS \"Schema\", COUNT(*) AS \"Tablas\"
FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'public')
GROUP BY schemaname
ORDER BY schemaname;
"
if [ "$LOAD_SEEDS" = true ]; then
echo ""
echo -e "${CYAN}=== DATOS CARGADOS ===${NC}"
psql "$DATABASE_URL" -c "
SELECT 'auth.users' as tabla, COUNT(*) as registros FROM auth.users
UNION ALL SELECT 'auth.roles', COUNT(*) FROM auth.roles
UNION ALL SELECT 'core.units', COUNT(*) FROM core.units
UNION ALL SELECT 'construction.conceptos', COUNT(*) FROM construction.conceptos
UNION ALL SELECT 'hr.employees', COUNT(*) FROM hr.employees
ORDER BY tabla;
" 2>/dev/null || true
fi
echo ""
echo -e "${GREEN}=============================================================================${NC}"
echo -e "${GREEN} RECREACION COMPLETADA EXITOSAMENTE${NC}"
echo -e "${GREEN}=============================================================================${NC}"
echo -e " Schemas: ${YELLOW}$schema_count${NC}"
echo -e " Tablas: ${YELLOW}$table_count${NC}"
[ "$LOAD_SEEDS" = true ] && echo -e " Seeds: ${YELLOW}$ENVIRONMENT${NC}"
echo -e "${GREEN}=============================================================================${NC}"
if [ "$LOAD_SEEDS" = true ] && [ "$ENVIRONMENT" = "dev" ]; then
echo ""
echo -e "${CYAN}Usuarios demo disponibles:${NC}"
echo " - admin@demo.com / Demo2026! (Super Admin)"
echo " - director@demo.com / Demo2026! (Director)"
echo " - gerente1@demo.com / Demo2026! (Gerente Obra)"
echo " - residente1@demo.com / Demo2026! (Residente)"
echo ""
echo -e "${CYAN}Tenant ID:${NC} 00000000-0000-0000-0003-000000000001"
echo ""
fi
}
# =============================================================================
# MAIN
# =============================================================================
main() {
parse_args "$@"
echo -e "${BLUE}=============================================================================${NC}"
echo -e "${BLUE} ERP CONSTRUCCION - Recreacion de Base de Datos${NC}"
echo -e "${BLUE}=============================================================================${NC}"
echo ""
local db_name=$(echo "$DATABASE_URL" | sed -E 's|.*/([^?]+).*|\1|')
local db_host=$(echo "$DATABASE_URL" | sed -E 's|.*@([^:]+):.*|\1|')
local db_port=$(echo "$DATABASE_URL" | sed -E 's|.*:([0-9]+)/.*|\1|')
echo -e "Database: ${YELLOW}$db_name${NC}"
echo -e "Host: ${YELLOW}$db_host:$db_port${NC}"
echo -e "Seeds: ${YELLOW}$([ "$LOAD_SEEDS" = true ] && echo "$ENVIRONMENT" || echo "No")${NC}"
echo ""
# Ejecutar pasos
verify_connection
confirm_drop
echo ""
drop_schemas
create_base_schemas
execute_init_ddl
execute_schema_ddl
create_complementary_tables
grant_permissions
if [ "$LOAD_SEEDS" = true ]; then
echo ""
load_seeds
fi
echo ""
verify_result
}
main "$@"