FASE 4-5-6: Infraestructura, CRM, Service Orders, Notificaciones, Ahorro, Logistica, API Publica

FASE 4:
- Redis cache de stock con fallback graceful
- Multi-moneda (MXN/USD) con contabilidad en MXN
- Proveedores y ordenes de compra completo
- Meilisearch 1.5M+ partes indexadas
- Metabase KPIs con dashboard auto-generado

FASE 5:
- CRM mejorado: activities, tags, loyalty program, analytics
- Imagenes de partes: upload, resize, thumbnails WebP
- Ordenes de servicio Kanban: received->diagnosis->repair->ready->delivered
- Garantias/RMA, alertas de reorden, multi-sucursal
- Stubs BNPL (APLAZO) y ERP Sync (Aspel/Contpaqi)

FASE 6:
- Notificaciones automaticas: push/WhatsApp/email/in-app
- Reportes de ahorro vs retail_price
- Logistica + tracking: DHL, FedEx, Estafeta, 99min, Uber
- API Publica: API keys, rate limiting, catalog search

Migraciones: v1.9-v3.0
Tests: 93/93 pasando
Backup: nexus_backup_20260427_045859.tar.gz
This commit is contained in:
Nexus Dev
2026-04-27 05:23:30 +00:00
parent b70cb3042b
commit 9ff3dc4c8b
71 changed files with 10939 additions and 420 deletions

302
scripts/backup_selective.sh Executable file
View File

@@ -0,0 +1,302 @@
#!/bin/bash
# ============================================================
# Nexus Autoparts — Selective Backup (No TecDoc)
# Backs up schema + all data EXCEPT vehicle_parts fitments
# Includes all tenant databases
# ============================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
err() { echo -e "${RED}[ERROR]${NC} $*"; }
fatal() { err "$*"; exit 1; }
# ─── Configuration ─────────────────────────────────────────────────────────
BACKUP_DIR="${BACKUP_DIR:-${PROJECT_DIR}/backups}"
RETENTION_DAYS="${RETENTION_DAYS:-30}"
MASTER_DB="${MASTER_DB:-nexus_autoparts}"
DB_USER="${DB_USER:-nexus}"
# Load .env if exists
if [[ -f "${PROJECT_DIR}/.env" ]]; then
set -a
source "${PROJECT_DIR}/.env"
set +a
fi
# Parse DB credentials from DATABASE_URL or MASTER_DB_URL
DB_URL="${MASTER_DB_URL:-${DATABASE_URL:-}}"
if [[ -n "$DB_URL" ]]; then
# Extract components from postgresql://user:pass@host/dbname
DB_HOST=$(echo "$DB_URL" | sed -n 's/.*@\([^:]*\).*/\1/p')
DB_PORT=$(echo "$DB_URL" | sed -n 's/.*:\([0-9]*\)\/.*/\1/p')
[[ -z "$DB_PORT" ]] && DB_PORT=5432
fi
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="nexus_backup_${TIMESTAMP}"
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_NAME}"
# ─── Pre-flight checks ─────────────────────────────────────────────────────
check_prerequisites() {
info "Checking prerequisites..."
if ! command -v pg_dump &>/dev/null; then
fatal "pg_dump not found. Install: sudo apt install postgresql-client"
fi
if ! command -v pg_dumpall &>/dev/null; then
fatal "pg_dumpall not found. Install: sudo apt install postgresql-client"
fi
# Test PostgreSQL connection
if ! sudo -u postgres psql -c "SELECT 1" &>/dev/null; then
fatal "Cannot connect to PostgreSQL. Is it running?"
fi
# Create backup directory
mkdir -p "$BACKUP_PATH"
ok "Prerequisites passed. Backup will be saved to: ${BACKUP_PATH}"
}
# ─── Backup master schema (structure only) ─────────────────────────────────
backup_master_schema() {
info "Backing up master database schema (structure only)..."
local output="${BACKUP_PATH}/01_master_schema.sql"
sudo -u postgres pg_dump \
--schema-only \
--no-owner \
--no-privileges \
"$MASTER_DB" > "$output"
local size=$(du -h "$output" | cut -f1)
ok "Master schema: ${size}"
}
# ─── Backup master data (excluding vehicle_parts) ──────────────────────────
backup_master_data() {
info "Backing up master database data (excluding vehicle_parts)..."
local output="${BACKUP_PATH}/02_master_data.sql"
local tables_file="${BACKUP_PATH}/_tables_to_backup.txt"
# Get list of tables EXCEPT vehicle_parts
sudo -u postgres psql "$MASTER_DB" -Atc "
SELECT tablename FROM pg_tables
WHERE schemaname = 'public'
AND tablename != 'vehicle_parts'
ORDER BY tablename;
" > "$tables_file"
local table_count=$(wc -l < "$tables_file")
info "Found ${table_count} tables to backup (excluded: vehicle_parts)"
# Build --table arguments
local table_args=""
while IFS= read -r table; do
[[ -n "$table" ]] && table_args="$table_args --table=$table"
done < "$tables_file"
# Dump data only for selected tables
sudo -u postgres pg_dump \
--data-only \
--no-owner \
--no-privileges \
$table_args \
"$MASTER_DB" > "$output"
# Compress
gzip -f "$output"
local size=$(du -h "${output}.gz" | cut -f1)
ok "Master data (no TecDoc): ${size}"
rm -f "$tables_file"
}
# ─── Backup vehicle_parts schema only ──────────────────────────────────────
backup_vehicle_parts_schema() {
info "Backing up vehicle_parts schema only (structure, no data)..."
local output="${BACKUP_PATH}/03_vehicle_parts_schema.sql"
sudo -u postgres pg_dump \
--schema-only \
--no-owner \
--no-privileges \
--table=vehicle_parts \
"$MASTER_DB" > "$output"
local size=$(du -h "$output" | cut -f1)
ok "vehicle_parts schema: ${size}"
}
# ─── Backup all tenant databases ───────────────────────────────────────────
backup_tenants() {
info "Discovering tenant databases..."
local tenants_file="${BACKUP_PATH}/_tenants.txt"
sudo -u postgres psql "$MASTER_DB" -Atc "
SELECT db_name FROM tenants WHERE is_active = true ORDER BY id;
" > "$tenants_file"
local tenant_count=$(wc -l < "$tenants_file")
if [[ "$tenant_count" -eq 0 ]]; then
warn "No active tenants found."
rm -f "$tenants_file"
return
fi
info "Found ${tenant_count} active tenant(s). Backing up..."
while IFS= read -r db_name; do
[[ -z "$db_name" ]] && continue
# Sanitize filename
local safe_name=$(echo "$db_name" | tr -cd 'a-z0-9_')
local output="${BACKUP_PATH}/tenant_${safe_name}.sql"
info " → Backing up ${db_name}..."
if sudo -u postgres psql -l | grep -q "^ ${db_name} "; then
sudo -u postgres pg_dump \
--no-owner \
--no-privileges \
"$db_name" > "$output"
gzip -f "$output"
local size=$(du -h "${output}.gz" | cut -f1)
ok "${db_name}: ${size}"
else
warn "${db_name}: database not found, skipping"
fi
done < "$tenants_file"
rm -f "$tenants_file"
}
# ─── Create manifest ───────────────────────────────────────────────────────
create_manifest() {
local manifest="${BACKUP_PATH}/MANIFEST.txt"
cat > "$manifest" << EOF
Nexus Autoparts — Selective Backup
Generated: $(date '+%Y-%m-%d %H:%M:%S')
Hostname: $(hostname)
CONTENTS:
---------
01_master_schema.sql — Database structure (all tables, indexes, constraints)
02_master_data.sql.gz — Data for all tables EXCEPT vehicle_parts
03_vehicle_parts_schema.sql — Structure of vehicle_parts only (no data)
tenant_*.sql.gz — Full backup of each active tenant database
RESTORE INSTRUCTIONS:
---------------------
1. Create empty database:
createdb nexus_autoparts
2. Restore schema:
psql nexus_autoparts < 01_master_schema.sql
3. Restore data:
gunzip -c 02_master_data.sql.gz | psql nexus_autoparts
4. Restore vehicle_parts structure (empty):
psql nexus_autoparts < 03_vehicle_parts_schema.sql
5. Restore tenants:
createdb tenant_name
gunzip -c tenant_name.sql.gz | psql tenant_name
RE-IMPORT TECDOC (optional):
----------------------------
To reload vehicle_parts data later:
python3 scripts/import_tecdoc.py download
python3 scripts/import_tecdoc.py import
EOF
ok "Manifest created"
}
# ─── Compress final archive ────────────────────────────────────────────────
create_archive() {
info "Creating final archive..."
local archive="${BACKUP_DIR}/${BACKUP_NAME}.tar.gz"
cd "$BACKUP_DIR"
tar -czf "$archive" "$BACKUP_NAME"
local archive_size=$(du -h "$archive" | cut -f1)
local unpacked_size=$(du -sh "$BACKUP_PATH" | cut -f1)
ok "Archive created: ${archive}"
echo ""
echo -e " ${BOLD}Compressed:${NC} ${archive_size}"
echo -e " ${BOLD}Unpacked:${NC} ${unpacked_size}"
echo ""
# Remove temp directory (keep archive)
rm -rf "$BACKUP_PATH"
}
# ─── Cleanup old backups ───────────────────────────────────────────────────
cleanup_old_backups() {
info "Cleaning up backups older than ${RETENTION_DAYS} days..."
local deleted=0
while IFS= read -r file; do
rm -f "$file"
((deleted++))
done < <(find "$BACKUP_DIR" -name "nexus_backup_*.tar.gz" -mtime +$RETENTION_DAYS)
if [[ "$deleted" -gt 0 ]]; then
ok "Deleted ${deleted} old backup(s)"
else
info "No old backups to delete"
fi
}
# ─── Main ──────────────────────────────────────────────────────────────────
main() {
echo ""
echo -e "${BOLD}${CYAN}"
echo " ========================================"
echo " Nexus Autoparts — Selective Backup"
echo " ========================================"
echo -e "${NC}"
echo ""
check_prerequisites
backup_master_schema
backup_master_data
backup_vehicle_parts_schema
backup_tenants
create_manifest
create_archive
cleanup_old_backups
echo -e "${BOLD}${GREEN}"
echo " Backup completed successfully!"
echo -e "${NC}"
echo " Location: ${BACKUP_DIR}/nexus_backup_${TIMESTAMP}.tar.gz"
echo ""
}
main "$@"