Files
ventas_php/classes/database-manager.class.php

321 lines
9.2 KiB
PHP
Executable File

<?php
require_once __DIR__ . '/system-config.class.php';
// Cargar MockDatabase si no hay conexión real
if (!class_exists('MockDatabase')) {
require_once __DIR__ . '/mock-database.class.php';
}
/**
* Gestor de base de datos multi-empresa compatible con PHP 8
* Reemplaza las funciones mysql_* obsoletas por mysqli_*
*/
class DatabaseManager {
private static $instance = null;
private $connections = [];
private $currentEmpresaId = null;
private $masterConnection = null;
/**
* Singleton pattern
*/
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor privado
*/
private function __construct() {}
/**
* Obtiene conexión a base de datos master
* @return mysqli
*/
public function getMasterConnection() {
if ($this->masterConnection === null) {
$config = SystemConfig::getMasterDatabaseConfig();
try {
$port = $config['port'] ?? 3306;
$this->masterConnection = new mysqli(
$config['host'],
$config['user'],
$config['password'],
$config['database'],
(int)$port
);
if ($this->masterConnection->connect_error) {
throw new Exception("Error conexión master DB: " . $this->masterConnection->connect_error);
}
$this->masterConnection->set_charset($config['charset']);
} catch (Exception $e) {
// Crear una conexión falsa para desarrollo sin BD
// die("CRITICAL DB ERROR: " . $e->getMessage()); // Debug removed
error_log("WARNING: No hay conexión a base de datos. Usando modo desarrollo. " . $e->getMessage());
$this->masterConnection = new MockDatabase();
}
}
return $this->masterConnection;
}
/**
* Obtiene conexión para empresa específica
* @param int $empresaId ID de la empresa
* @return mysqli
*/
public function getEmpresaConnection($empresaId) {
if (!isset($this->connections[$empresaId])) {
$config = SystemConfig::getEmpresaDatabaseConfig($empresaId);
// Validar que exista la base de datos (con fallback)
if (!SystemConfig::validateDatabaseExists($config['database'])) {
// Intentar fallback a base de datos master
$masterConfig = SystemConfig::getMasterDatabaseConfig();
error_log("Base de datos {$config['database']} no encontrada, usando fallback a master");
$config = array_merge($config, [
'database' => $masterConfig['database'],
'user' => $masterConfig['user'],
'password' => $masterConfig['password']
]);
}
$mysqli = new mysqli(
$config['host'] . ':' . $config['port'],
$config['user'],
$config['password'],
$config['database']
);
if ($mysqli->connect_error) {
throw new Exception("Error conexión empresa $empresaId: " . $mysqli->connect_error);
}
$mysqli->set_charset($config['charset']);
$this->connections[$empresaId] = $mysqli;
}
return $this->connections[$empresaId];
}
/**
* Establece empresaId actual basado en usuario en sesión
* @param int $userId ID del usuario en sesión
*/
public function setEmpresaByUser($userId) {
$this->currentEmpresaId = SystemConfig::getEmpresaIdByUserId($userId);
if (!$this->currentEmpresaId) {
throw new Exception("No se pudo determinar empresaId para usuario: $userId");
}
}
/**
* Establece empresaId actual directamente
* @param int $empresaId ID de la empresa
*/
public function setEmpresaId($empresaId) {
$this->currentEmpresaId = (int)$empresaId;
}
/**
* Obtiene empresaId actual
* @return int|null
*/
public function getEmpresaId() {
return $this->currentEmpresaId;
}
/**
* Obtiene conexión para empresa actual
* @return mysqli
*/
public function getCurrentConnection() {
if (!$this->currentEmpresaId) {
throw new Exception("No hay empresaId establecido. Use setEmpresaByUser() o setEmpresaId()");
}
return $this->getEmpresaConnection($this->currentEmpresaId);
}
/**
* Cierra todas las conexiones
*/
public function closeAll() {
if ($this->masterConnection) {
$this->masterConnection->close();
$this->masterConnection = null;
}
foreach ($this->connections as $connection) {
$connection->close();
}
$this->connections = [];
$this->currentEmpresaId = null;
}
/**
* Destructor para asegurar cierre de conexiones
*/
public function __destruct() {
$this->closeAll();
}
}
/**
* Clase ModernDB compatible con código existente
* Facade sobre DatabaseManager para mantener compatibilidad
*/
class ModernDB {
private $connection;
private $isMaster = false;
private $empresaId = null;
/**
* Constructor
* @param bool $useMaster Si true, usa conexión master
* @param int $empresaId ID de la empresa (si no es master)
*/
public function __construct($useMaster = false, $empresaId = null) {
$dbManager = DatabaseManager::getInstance();
if ($useMaster) {
$this->connection = $dbManager->getMasterConnection();
$this->isMaster = true;
} else {
if ($empresaId === null) {
$empresaId = $dbManager->getEmpresaId();
}
if ($empresaId === null) {
throw new Exception("Se requiere empresaId para conexión de empresa");
}
$this->connection = $dbManager->getEmpresaConnection($empresaId);
$this->empresaId = $empresaId;
}
}
/**
* Ejecuta una consulta SQL
* @param string $sql Consulta SQL
* @return mysqli_result|false
*/
public function Query($sql) {
$result = $this->connection->query($sql);
if ($result === false) {
error_log("Error en consulta: " . $this->connection->error);
error_log("SQL: " . $sql);
}
return $result;
}
/**
* Obtiene una fila como arreglo asociativo
* @param mysqli_result $result Resultado de consulta
* @return array|null
*/
public function FetchAssoc($result) {
return $result->fetch_assoc();
}
/**
* Obtiene el número de filas de un resultado
* @param mysqli_result $result Resultado de consulta
* @return int
*/
public function NumRows($result) {
return $result->num_rows;
}
/**
* Obtiene un valor específico de una fila
* @param mysqli_result $result Resultado de consulta
* @param int $row Número de fila
* @param mixed $field Campo (nombre o índice)
* @return mixed
*/
public function Result($result, $row, $field = 0) {
$result->data_seek($row);
$row_data = $result->fetch_array();
return is_numeric($field) ? $row_data[$field] : $row_data[$field];
}
/**
* Obtiene el último ID insertado
* @return int
*/
public function InsertId() {
return $this->connection->insert_id;
}
/**
* Obtiene el número de filas afectadas
* @return int
*/
public function AffectedRows() {
return $this->connection->affected_rows;
}
/**
* Libera memoria del resultado
* @param mysqli_result $result Resultado a liberar
*/
public function FreeResult($result) {
$result->free();
}
/**
* Escapa caracteres especiales para prevenir SQL injection
* @param string $string String a escapar
* @return string
*/
public function Escape($string) {
return $this->connection->real_escape_string($string);
}
/**
* Obtiene el último error de MySQL
* @return string
*/
public function GetError() {
return $this->connection->error;
}
/**
* Inicia una transacción
*/
public function BeginTransaction() {
$this->connection->begin_transaction();
}
/**
* Confirma una transacción
*/
public function Commit() {
$this->connection->commit();
}
/**
* Revierte una transacción
*/
public function Rollback() {
$this->connection->rollback();
}
/**
* Cierra la conexión
*/
public function Close() {
// No cerramos conexiones individuales ya que las maneja DatabaseManager
}
}