Initial commit - Horux Despachos NL

This commit is contained in:
2026-05-03 16:47:53 -06:00
commit b00b677c54
647 changed files with 133843 additions and 0 deletions

71
scripts/backup.sh Normal file
View File

@@ -0,0 +1,71 @@
#!/bin/bash
# Horux360 Database Backup Script
# Backs up all databases (central + tenant) with daily/weekly rotation
# Requires: .pgpass file at /root/.pgpass with format: localhost:5432:*:postgres:<password>
#
# Usage: Add to crontab:
# 0 1 * * * /root/Horux/scripts/backup.sh >> /var/log/horux-backup.log 2>&1
set -euo pipefail
BACKUP_DIR="/var/horux/backups"
DAILY_DIR="$BACKUP_DIR/daily"
WEEKLY_DIR="$BACKUP_DIR/weekly"
PG_USER="postgres"
PG_HOST="localhost"
DATE=$(date +%Y-%m-%d)
DAY_OF_WEEK=$(date +%u)
# Retention
DAILY_KEEP=7
WEEKLY_KEEP=4
echo "=== Horux360 Backup Started: $(date) ==="
# Create directories
mkdir -p "$DAILY_DIR" "$WEEKLY_DIR"
# Get list of all horux databases (central + tenant)
DATABASES=$(psql -h "$PG_HOST" -U "$PG_USER" -t -c \
"SELECT datname FROM pg_database WHERE datname = 'horux360' OR datname LIKE 'horux_%' AND datname NOT LIKE '%_deleted_%'" \
| tr -d ' ')
TOTAL=0
ERRORS=0
for DB in $DATABASES; do
echo "Backing up: $DB"
DUMP_FILE="$DAILY_DIR/${DB}_${DATE}.sql.gz"
if pg_dump -h "$PG_HOST" -U "$PG_USER" "$DB" | gzip > "$DUMP_FILE"; then
# Verify file is not empty
if [ -s "$DUMP_FILE" ]; then
TOTAL=$((TOTAL + 1))
echo " OK: $(du -h "$DUMP_FILE" | cut -f1)"
else
echo " WARNING: Empty backup file for $DB"
rm -f "$DUMP_FILE"
ERRORS=$((ERRORS + 1))
fi
else
echo " ERROR: Failed to backup $DB"
ERRORS=$((ERRORS + 1))
fi
done
# Weekly backup on Sundays (day 7)
if [ "$DAY_OF_WEEK" -eq 7 ]; then
echo "Creating weekly backup..."
cp "$DAILY_DIR"/*_"${DATE}".sql.gz "$WEEKLY_DIR/" 2>/dev/null || true
fi
# Clean old daily backups
echo "Cleaning daily backups older than $DAILY_KEEP days..."
find "$DAILY_DIR" -name "*.sql.gz" -mtime "+$DAILY_KEEP" -delete
# Clean old weekly backups
echo "Cleaning weekly backups older than $WEEKLY_KEEP weeks..."
find "$WEEKLY_DIR" -name "*.sql.gz" -mtime "+$((WEEKLY_KEEP * 7))" -delete
echo "=== Backup Complete: $TOTAL databases backed up, $ERRORS errors ==="
echo "=== Finished: $(date) ==="

27
scripts/tune-postgres.sh Normal file
View File

@@ -0,0 +1,27 @@
#!/bin/bash
# PostgreSQL Production Tuning for Horux360
# Server: 32GB RAM, 8 cores
# Target: 50 tenants, PM2 cluster ×2
#
# Run once: sudo bash scripts/tune-postgres.sh
set -euo pipefail
echo "=== PostgreSQL Production Tuning ==="
sudo -u postgres psql -c "ALTER SYSTEM SET max_connections = 300;"
sudo -u postgres psql -c "ALTER SYSTEM SET shared_buffers = '4GB';"
sudo -u postgres psql -c "ALTER SYSTEM SET work_mem = '16MB';"
sudo -u postgres psql -c "ALTER SYSTEM SET effective_cache_size = '16GB';"
sudo -u postgres psql -c "ALTER SYSTEM SET maintenance_work_mem = '512MB';"
sudo -u postgres psql -c "ALTER SYSTEM SET checkpoint_completion_target = 0.9;"
sudo -u postgres psql -c "ALTER SYSTEM SET wal_buffers = '64MB';"
sudo -u postgres psql -c "ALTER SYSTEM SET random_page_cost = 1.1;"
echo "Settings applied. Restarting PostgreSQL..."
sudo systemctl restart postgresql
echo "Verifying settings..."
sudo -u postgres psql -c "SHOW max_connections; SHOW shared_buffers; SHOW work_mem;"
echo "=== Done ==="

135
scripts/update-cfdi-xml.js Normal file
View File

@@ -0,0 +1,135 @@
#!/usr/bin/env node
/**
* Script para actualizar CFDIs existentes con su XML original
* Uso: node scripts/update-cfdi-xml.js <directorio> <schema>
* Ejemplo: node scripts/update-cfdi-xml.js /root/xmls tenant_roem691011ez4
*/
const fs = require('fs');
const path = require('path');
const { Pool } = require('pg');
// Configuración de la base de datos
const pool = new Pool({
host: 'localhost',
port: 5432,
database: 'horux360',
user: 'postgres',
password: 'postgres',
});
// Extraer UUID del XML
function extractUuidFromXml(xmlContent) {
// Buscar UUID en TimbreFiscalDigital
const uuidMatch = xmlContent.match(/UUID=["']([A-Fa-f0-9-]{36})["']/i);
return uuidMatch ? uuidMatch[1].toUpperCase() : null;
}
// Procesar archivos en lotes
async function processFiles(directory, schema, batchSize = 500) {
const files = fs.readdirSync(directory).filter(f => f.toLowerCase().endsWith('.xml'));
console.log(`\nEncontrados ${files.length} archivos XML en ${directory}`);
console.log(`Schema: ${schema}`);
console.log(`Tamaño de lote: ${batchSize}\n`);
let updated = 0;
let notFound = 0;
let errors = 0;
let processed = 0;
const startTime = Date.now();
// Procesar en lotes
for (let i = 0; i < files.length; i += batchSize) {
const batch = files.slice(i, i + batchSize);
const updates = [];
// Leer y parsear XMLs del lote
for (const file of batch) {
try {
const filePath = path.join(directory, file);
const xmlContent = fs.readFileSync(filePath, 'utf8');
const uuid = extractUuidFromXml(xmlContent);
if (uuid) {
updates.push({ uuid, xmlContent });
} else {
errors++;
if (errors <= 5) {
console.log(` ⚠ No se encontró UUID en: ${file}`);
}
}
} catch (err) {
errors++;
if (errors <= 5) {
console.log(` ✗ Error leyendo ${file}: ${err.message}`);
}
}
}
// Actualizar base de datos en una transacción
const client = await pool.connect();
try {
await client.query('BEGIN');
for (const { uuid, xmlContent } of updates) {
const result = await client.query(
`UPDATE "${schema}".cfdis SET xml_original = $1 WHERE UPPER(uuid_fiscal) = $2 AND xml_original IS NULL`,
[xmlContent, uuid]
);
if (result.rowCount > 0) {
updated++;
} else {
notFound++;
}
}
await client.query('COMMIT');
} catch (err) {
await client.query('ROLLBACK');
console.error(`Error en lote: ${err.message}`);
errors += batch.length;
} finally {
client.release();
}
processed += batch.length;
// Progreso
const percent = ((processed / files.length) * 100).toFixed(1);
const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
const rate = (processed / elapsed).toFixed(0);
process.stdout.write(`\r Procesando: ${processed}/${files.length} (${percent}%) - ${updated} actualizados - ${rate} archivos/seg `);
}
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
console.log(`\n\n✓ Completado en ${totalTime} segundos`);
console.log(` - Actualizados: ${updated}`);
console.log(` - No encontrados (UUID no existe en BD): ${notFound}`);
console.log(` - Errores: ${errors}`);
await pool.end();
}
// Main
const args = process.argv.slice(2);
if (args.length < 2) {
console.log('Uso: node scripts/update-cfdi-xml.js <directorio> <schema>');
console.log('Ejemplo: node scripts/update-cfdi-xml.js /root/xmls tenant_roem691011ez4');
process.exit(1);
}
const [directory, schema] = args;
if (!fs.existsSync(directory)) {
console.error(`Error: El directorio ${directory} no existe`);
process.exit(1);
}
processFiles(directory, schema).catch(err => {
console.error('Error fatal:', err);
process.exit(1);
});