Primer commit del sistema avantika sin cambios
This commit is contained in:
100
php8-migration/.env.example
Normal file
100
php8-migration/.env.example
Normal file
@@ -0,0 +1,100 @@
|
||||
# Ejemplo de archivo .env para sistema multi-empresa
|
||||
# COPIAR ESTE CONTENIDO A UN ARCHIVO .env EN LA RAÍZ DEL PROYECTO
|
||||
|
||||
# =============================================================================
|
||||
# CONFIGURACIÓN DE BASES DE DATOS MULTI-EMPRESA
|
||||
# =============================================================================
|
||||
# NOTA: No incluir información sensible en el código
|
||||
# Todas las credenciales deben estar en este archivo .env
|
||||
|
||||
# Base de datos Master (para autenticación y catálogos globales)
|
||||
# Basado en estructura real: avantikads_nmgen
|
||||
DB_MASTER_HOST=localhost
|
||||
DB_MASTER_DATABASE=avantikads_nmgen
|
||||
DB_MASTER_USER=root
|
||||
DB_MASTER_PASSWORD=venomp
|
||||
|
||||
# Configuración general de conexión
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_CHARSET=utf8mb4
|
||||
|
||||
# Prefijo para bases de datos de empresas
|
||||
# Patón real: avantikads_nm{empresaId}
|
||||
DB_EMPRESA_PREFIX=avantikads_nm
|
||||
DB_EMPRESA_USER=root
|
||||
DB_EMPRESA_PASSWORD=venomp
|
||||
|
||||
# =============================================================================
|
||||
# CONFIGURACIÓN DEL SISTEMA
|
||||
# =============================================================================
|
||||
|
||||
# Rutas del sistema
|
||||
DOC_ROOT=/var/www/html/ventas
|
||||
WEB_ROOT=http://localhost
|
||||
|
||||
# Configuración SMTP (correos)
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_USER=tu_correo@gmail.com
|
||||
SMTP_PASS=tu_password_app
|
||||
SMTP_PORT=587
|
||||
|
||||
# Configuración PAC (Facturación electrónica)
|
||||
USER_PAC=tu_usuario_pac
|
||||
PW_PAC=tu_password_pac
|
||||
|
||||
# Configuración de paginación
|
||||
ITEMS_PER_PAGE=20
|
||||
|
||||
# Rango de años válidos
|
||||
MIN_YEAR=2025
|
||||
MAX_YEAR=2030
|
||||
|
||||
# Modo de depuración (true/false)
|
||||
DEBUG_MODE=false
|
||||
|
||||
# =============================================================================
|
||||
# CONFIGURACIÓN ADICIONAL
|
||||
# =============================================================================
|
||||
|
||||
# Configuración de sesión
|
||||
SESSION_LIFETIME=3600
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=
|
||||
|
||||
# Configuración de seguridad
|
||||
ENCRYPTION_KEY=tu_clave_encriptacion_32_chars
|
||||
JWT_SECRET=tu_jwt_secret_key_aqui
|
||||
|
||||
# Configuración de archivos
|
||||
MAX_FILE_SIZE=10485760
|
||||
ALLOWED_FILE_TYPES=pdf,xml,csv,xlsx
|
||||
|
||||
# Configuración de respaldos
|
||||
BACKUP_PATH=/var/backups/ventas
|
||||
AUTO_BACKUP=true
|
||||
BACKUP_RETENTION_DAYS=30
|
||||
|
||||
# =============================================================================
|
||||
# VARIABLES ESPECÍFICAS DEL NEGOCIO
|
||||
# =============================================================================
|
||||
|
||||
# Tasas de impuestos (pueden venir de BD pero tener valores por defecto)
|
||||
IVA_RATE=0.16
|
||||
ISR_RATE=0.10
|
||||
|
||||
# Configuración de facturación
|
||||
SERIE_FACTURA=A
|
||||
SERIE_NOTA_CREDITO=B
|
||||
FOLIO_INICIAL=1
|
||||
|
||||
# Información bancaria por defecto (actualizar según empresa)
|
||||
BANK_NAME=Banamex
|
||||
BANK_ACCOUNT=224996
|
||||
BANK_CLABE=002100017902249960
|
||||
BANK_BRANCH=0179
|
||||
|
||||
# Contacto por defecto
|
||||
CONTACT_PHONE=(961) 10 5 58 20
|
||||
CONTACT_EMAIL=contacto@empresa.com
|
||||
CONTACT_WEB=www.empresa.com
|
||||
136
php8-migration/analisis-base-datos.md
Normal file
136
php8-migration/analisis-base-datos.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# Análisis de Base de Datos - Sistema Multi-Empresa
|
||||
|
||||
## Estructura de Bases de Datos Identificada
|
||||
|
||||
### Base de Datos Principal: `avantikads_nmgen`
|
||||
**Propósito:** Base de datos master/global del sistema
|
||||
**Tablas clave:**
|
||||
- `empresa` - Catálogo de empresas (empresaId como PK)
|
||||
- `usuario` - Usuarios del sistema con empresaId asociado
|
||||
- Configuración global y shared resources
|
||||
|
||||
### Base de Datos de Empresa: `avantikads_nm15`
|
||||
**Propósito:** Datos específicos de empresa con ID 15
|
||||
**Contenido:** Tablas operativas como:
|
||||
- `_prodSinInv` - Productos sin inventario
|
||||
- Todas las tablas transaccionales de la empresa 15
|
||||
|
||||
## Arquitectura Multi-Empresa Confirmada
|
||||
|
||||
### Estructura Validada
|
||||
```
|
||||
avantikads_nmgen (Master DB)
|
||||
├── empresa (empresaId, activo, datos generales)
|
||||
├── usuario (usuarioId, empresaId, datos usuario)
|
||||
└── [Tablas globales]
|
||||
|
||||
avantikads_nm{empresaId} (Empresa-specific DB)
|
||||
├── _prodSinInv
|
||||
├── productos
|
||||
├── pedidos
|
||||
├── ventas
|
||||
└── [Todas las tablas operativas]
|
||||
```
|
||||
|
||||
### Flujo de Autenticación y Selección
|
||||
1. **Usuario inicia sesión** → Conexión a `avantikads_nmgen`
|
||||
2. **Validación de credenciales** en tabla `usuario`
|
||||
3. **Obtención de empresaId** del registro del usuario
|
||||
4. **Conexión dinámica** a `avantikads_nm{empresaId}`
|
||||
5. **Operaciones en BD de la empresa específica**
|
||||
|
||||
## Implicaciones para la Migración
|
||||
|
||||
### 1. Base de Datos Master Obligatoria
|
||||
- **Mantener `avantikads_nmgen` como master**
|
||||
- **Función**: Autenticación y routing de empresas
|
||||
- **Acceso**: Todos los usuarios se conectan primero aquí
|
||||
|
||||
### 2. Configuración Dinámica por empresaId
|
||||
- **Patón confirmado**: `SQL_DATABASE2 . empresaId`
|
||||
- **Ejemplo real**: `avantikads_nm15` para empresaId = 15
|
||||
- **Necesidad**: Routing automático basado en usuario
|
||||
|
||||
### 3. Archivos de Configuración Adicionales
|
||||
|
||||
#### `/base_datos/` - Esquemas de Referencia
|
||||
- `avantikads_nmgen.sql` - Estructura master
|
||||
- `avantikads_nm15.sql` - Ejemplo estructura empresa
|
||||
- **Uso**: Validar estructura durante migración
|
||||
|
||||
## Actualizaciones al Plan de Migración
|
||||
|
||||
### Configuración .env - Ajustes Necesarios
|
||||
|
||||
#### Variables de Conexión Master
|
||||
```env
|
||||
# Base de datos Master (global para autenticación)
|
||||
DB_MASTER_HOST=localhost
|
||||
DB_MASTER_DATABASE=avantikads_nmgen
|
||||
DB_MASTER_USER=admin_user
|
||||
DB_MASTER_PASSWORD=secure_master_password
|
||||
|
||||
# Prefijo para bases de datos de empresas
|
||||
DB_EMPRESA_PREFIX=avantikads_nm
|
||||
```
|
||||
|
||||
#### Lógica de Conexión por Empresa
|
||||
```php
|
||||
// Función actualizada en ejemplo-env-config.php
|
||||
public static function getDatabaseConfig($empresaId) {
|
||||
$masterConfig = [
|
||||
'database' => $_ENV['DB_MASTER_DATABASE'] . '_' . $empresaId
|
||||
];
|
||||
|
||||
// Validar que exista la base de datos
|
||||
if (!self::validateDatabaseExists($masterConfig['database'])) {
|
||||
throw new Exception("Base de datos no existe: " . $masterConfig['database']);
|
||||
}
|
||||
|
||||
return $masterConfig;
|
||||
}
|
||||
```
|
||||
|
||||
### Validaciones de Migración
|
||||
|
||||
#### 1. Verificación de Estructura
|
||||
- Comparar esquemas actuales con archivos en `/base_datos/`
|
||||
- Validar que todas las tablas requeridas existan
|
||||
- Verificar integridad de datos por empresa
|
||||
|
||||
#### 2. Testing Multi-Empresa
|
||||
- Crear usuarios de prueba para diferentes empresas
|
||||
- Validar routing correcto de conexiones
|
||||
- Probar aislamiento de datos entre empresas
|
||||
|
||||
#### 3. Migración Gradual
|
||||
- Iniciar con empresa 15 (tenemos su esquema)
|
||||
- Validar funcionamiento completo
|
||||
- Extender a otras empresas
|
||||
|
||||
## Archivos de Migración Adicionales
|
||||
|
||||
### `/base_datos/` - Incluir en plan
|
||||
- **Mantener como referencia** durante migración
|
||||
- **Usar para validación** de estructura post-migración
|
||||
- **Documentar esquema** actual para comparación
|
||||
|
||||
### Scripts de Migración (Sugeridos)
|
||||
- `migrar_master_db.php` - Migración base master
|
||||
- `validar_esquemas.php` - Validación contra archivos SQL
|
||||
- `test_multi_empresa.php` - Testing routing de conexiones
|
||||
|
||||
## Impacto en Estimación de Tiempo
|
||||
|
||||
### Tiempo Adicional Requerido
|
||||
- **Validación de esquemas:** +5-10 horas
|
||||
- **Testing multi-empresa:** +10-15 horas
|
||||
- **Migración de datos:** +8-12 horas
|
||||
- **Total adicional:** +23-37 horas
|
||||
|
||||
### Estimación Actualizada: 113-162 horas
|
||||
|
||||
## Recomendación
|
||||
|
||||
**La estructura `/base_datos/` confirma la arquitectura multi-empresa.**
|
||||
El plan actualizado debe incluir validación específica contra estos esquemas para asegurar migración correcta.
|
||||
99
php8-migration/analisis-sistema.md
Normal file
99
php8-migration/analisis-sistema.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Análisis de Sistema para Migración a PHP 8
|
||||
|
||||
## Información General del Sistema
|
||||
- **Directorio Principal**: /var/www/html/ventas
|
||||
- **Versión PHP Actual**: 8.3.6
|
||||
- **Tipo de Sistema**: Sistema de ventas/inventario
|
||||
|
||||
## Estructura Identificada
|
||||
|
||||
### Archivos PHP Principales (excluyendo templates_c y tcpdf)
|
||||
- `index.php` - Archivo de entrada principal
|
||||
- `libraries.php` - Carga de clases y librerías
|
||||
- Múltiples archivos en directorios:
|
||||
- `ajax/` - Endpoints AJAX
|
||||
- `classes/` - Clases del sistema
|
||||
- `pdf/` - Generación de PDFs
|
||||
- `properties/` - Configuración
|
||||
|
||||
### Librerías Detectadas
|
||||
- Smarty (motor de plantillas)
|
||||
- NuSOAP (webservices)
|
||||
- PHPMailer
|
||||
- TCPDF
|
||||
- FPDI
|
||||
- Clases personalizadas del sistema
|
||||
|
||||
## Potenciales Problemas de Compatibilidad con PHP 8
|
||||
|
||||
### 1. Funciones Obsoletas/Eliminadas
|
||||
- `mysql_*` functions (si existen)
|
||||
- `ereg*` functions
|
||||
- `each()` function
|
||||
- `create_function()`
|
||||
- Magic quotes (si están siendo usadas)
|
||||
|
||||
### 2. Cambios en Manejo de Errores
|
||||
- Concatenación con null
|
||||
- Operadores de comparación no estricta
|
||||
- Orden de evaluación de operandos
|
||||
|
||||
### 3. Cambios en Tipado
|
||||
- Declaraciones de tipo más estrictas
|
||||
- Retorno por referencia
|
||||
- Parámetros por referencia
|
||||
|
||||
### 4. Extensiones
|
||||
- mcrypt (eliminada en PHP 8)
|
||||
- mysql (eliminada)
|
||||
- mssql (eliminada)
|
||||
|
||||
## Archivos Críticos a Analizar
|
||||
|
||||
### Nivel 1 - Core del Sistema
|
||||
1. `index.php` - Punto de entrada
|
||||
2. `libraries.php` - Carga de dependencias
|
||||
3. `properties/config.php` - Configuración
|
||||
4. `init.php` - Inicialización
|
||||
|
||||
### Nivel 2 - Clases Principales
|
||||
- `classes/db.class.php` - Conexión a BD
|
||||
- `classes/user.class.php` - Gestión de usuarios
|
||||
- `classes/producto.class.php` - Gestión de productos
|
||||
- `classes/pedido.class.php` - Gestión de pedidos
|
||||
|
||||
### Nivel 3 - Endpoints AJAX
|
||||
- Todos los archivos en `ajax/`
|
||||
|
||||
### Nivel 4 - Generación de Reportes
|
||||
- Archivos en `pdf/`
|
||||
- Archivos en `reportes/`
|
||||
|
||||
## Plan de Migración Propuesto
|
||||
|
||||
### Fase 1: Análisis Detallado
|
||||
1. Escanear todos los archivos PHP en busca de:
|
||||
- Funciones obsoletas
|
||||
- Sintaxis incompatible
|
||||
- Extensiones eliminadas
|
||||
|
||||
### Fase 2: Creación de Backups
|
||||
1. Backup completo del sistema
|
||||
2. Versionado de archivos modificados
|
||||
|
||||
### Fase 3: Migración Gradual
|
||||
1. Actualizar configuración PHP
|
||||
2. Reemplazar funciones obsoletas
|
||||
3. Actualizar sintaxis
|
||||
4. Probar funcionalidad
|
||||
|
||||
### Fase 4: Validación
|
||||
1. Pruebas unitarias
|
||||
2. Pruebas de integración
|
||||
3. Pruebas de sistema completo
|
||||
|
||||
## Recomendaciones Iniciales
|
||||
1. NO hacer modificaciones sin aprobación previa
|
||||
2. Crear un entorno de prueba aislado
|
||||
3. Hacer backup completo antes de cualquier cambio
|
||||
4. Documentar todos los cambios realizados
|
||||
97
php8-migration/archivos-criticos.md
Normal file
97
php8-migration/archivos-criticos.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Lista de Archivos Críticos para Migración PHP 8 y Configuración .env
|
||||
|
||||
## Nivel CRÍTICO - Modificación Obligatoria
|
||||
|
||||
### 1. Archivos de Configuración
|
||||
- `config.php` - ELIMINAR y reemplazar por configuración .env
|
||||
- `.env` - CREAR con todas las credenciales seguras
|
||||
- `init.php` - Actualizar para cargar configuración .env
|
||||
|
||||
### 2. Base de Datos y Conexiones
|
||||
- `classes/db.class.php` - REESCRIBIR completamente con mysqli y multi-empresa
|
||||
- `classes/util.class.php` - Actualizar DBSelect() y configuración
|
||||
- `libraries.php` - Actualizar includes de configuración
|
||||
|
||||
### 3. Autenticación y Sesiones
|
||||
- `index.php` - Actualizar lógica de sesión y empresaId
|
||||
- `login.php` - Implementar determinación de empresaId desde usuario
|
||||
- Cualquier archivo que maneje `$_SESSION['empresaId']` (100+ archivos)
|
||||
|
||||
## Nivel ALTO - Funciones Obsoletas
|
||||
|
||||
### Funciones Eliminadas en PHP 8
|
||||
- `classes/util.class.php:575` - ereg_replace()
|
||||
- `ajax/cuentas-pagar.php:18` - split()
|
||||
- `ajax/evaluar-pedidos.php:160,360` - split()
|
||||
- `classes/comprobante.class.php:350` - split()
|
||||
- `pdf/domdf.php:227` - split()
|
||||
|
||||
### each() function (7 archivos)
|
||||
- `pdf/include/style.cls.php:790`
|
||||
- `pdf/fpdf.php:1565`
|
||||
- `pdf/fpdi.php:435`
|
||||
- `classes/class.phpmailer.php:1645`
|
||||
- `classes/class.smtp.php:388,417`
|
||||
- `classes/comprobante.class.php:1465`
|
||||
|
||||
### create_function() (4 archivos)
|
||||
- `pdf/include/frame_reflower.cls.php:233`
|
||||
- `pdf/include/text_frame_reflower.cls.php:380,388,416`
|
||||
|
||||
## Nivel MEDIO - Sintaxis Incompatible
|
||||
|
||||
### Llaves para acceso a strings/arrays (100+ archivos)
|
||||
Principalmente en:
|
||||
- `tcpdf/barcodes.php` - 50+ ocurrencias
|
||||
- `classes/json.class.php` - 20+ ocurrencias
|
||||
- Múltiples archivos en `tcpdf/`, `pdf/`, `libs/`
|
||||
|
||||
### list() assignments (98+ ocurrencias)
|
||||
- `pdf/include/gd_adapter.cls.php` - Múltiples líneas
|
||||
- `pdf/include/functions.inc.php:725`
|
||||
- Múltiples archivos PDF
|
||||
|
||||
## Archivos por Directorio (Priorizados)
|
||||
|
||||
### / (Raíz)
|
||||
- `index.php` - Entrada principal
|
||||
- `config.php` - Eliminar
|
||||
- `libraries.php` - Actualizar
|
||||
- `init.php` - Actualizar
|
||||
|
||||
### /classes/
|
||||
- `db.class.php` - Reescribir completamente
|
||||
- `util.class.php` - Actualizar DBSelect()
|
||||
- `json.class.php` - Sintaxis llaves
|
||||
- `class.phpmailer.php` - each() function
|
||||
- `comprobante.class.php` - split(), each()
|
||||
- `class.smtp.php` - each() function
|
||||
|
||||
### /ajax/
|
||||
- `cuentas-pagar.php` - split() function
|
||||
- `evaluar-pedidos.php` - split() function
|
||||
- Todos los archivos con $_SESSION['empresaId'] (50+)
|
||||
|
||||
### /modules/
|
||||
- Todos los archivos con $_SESSION['empresaId'] (100+)
|
||||
- Archivos de facturación, inventario, usuarios, etc.
|
||||
|
||||
### /pdf/
|
||||
- `fpdf.php` - each() function
|
||||
- `domdf.php` - split() function
|
||||
- `fpdi.php` - each() function
|
||||
- Todos los archivos con sintaxis de llaves
|
||||
|
||||
### /tcpdf/
|
||||
- `barcodes.php` - 50+ sintaxis de llaves
|
||||
- Múltiples archivos con sintaxis incompatible
|
||||
|
||||
## Total Estimado
|
||||
- **Archivos críticos:** 10-15
|
||||
- **Archivos con funciones obsoletas:** 20+
|
||||
- **Archivos con sintaxis incompatible:** 100+
|
||||
- **Archivos con $_SESSION['empresaId']:** 100+
|
||||
|
||||
## Nota Importante
|
||||
Esta lista representa el mínimo de archivos que deben ser modificados.
|
||||
Durante el proceso de migración pueden descubrirse archivos adicionales que requieran cambios.
|
||||
195
php8-migration/ejemplo-db-mysqli.php
Normal file
195
php8-migration/ejemplo-db-mysqli.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* Ejemplo de migración de db.class.php de MySQL a MySQLi
|
||||
*
|
||||
* Este archivo muestra cómo migrar las funciones mysql_* obsoletas
|
||||
* a mysqli_* compatible con PHP 8
|
||||
*/
|
||||
|
||||
// Versión original con mysql_* (NO COMPATIBLE CON PHP 8)
|
||||
/*
|
||||
class DB {
|
||||
private $connection;
|
||||
|
||||
function Open($host, $user, $pass, $db) {
|
||||
$this->connection = mysql_connect($host, $user, $pass);
|
||||
mysql_select_db($db, $this->connection);
|
||||
}
|
||||
|
||||
function Query($sql) {
|
||||
return mysql_query($sql, $this->connection);
|
||||
}
|
||||
|
||||
function FetchAssoc($result) {
|
||||
return mysql_fetch_assoc($result);
|
||||
}
|
||||
|
||||
function NumRows($result) {
|
||||
return mysql_num_rows($result);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Versión actualizada con mysqli_* (COMPATIBLE CON PHP 8)
|
||||
class DB {
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* Abre conexión a la base de datos
|
||||
* @param string $host Host de la base de datos
|
||||
* @param string $user Usuario de la base de datos
|
||||
* @param string $pass Contraseña de la base de datos
|
||||
* @param string $db Nombre de la base de datos
|
||||
* @return boolean
|
||||
*/
|
||||
function Open($host, $user, $pass, $db) {
|
||||
$this->connection = mysqli_connect($host, $user, $pass, $db);
|
||||
|
||||
if (!$this->connection) {
|
||||
// Manejo de error compatible
|
||||
die("Error de conexión: " . mysqli_connect_error());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Establecer charset para compatibilidad UTF-8
|
||||
mysqli_set_charset($this->connection, "utf8");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ejecuta una consulta SQL
|
||||
* @param string $sql Consulta SQL
|
||||
* @return mysqli_result|false
|
||||
*/
|
||||
function Query($sql) {
|
||||
$result = mysqli_query($this->connection, $sql);
|
||||
|
||||
if ($result === false) {
|
||||
// Manejo de error compatible
|
||||
error_log("Error en consulta: " . mysqli_error($this->connection));
|
||||
error_log("SQL: " . $sql);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene una fila como arreglo asociativo
|
||||
* @param mysqli_result $result Resultado de consulta
|
||||
* @return array|null
|
||||
*/
|
||||
function FetchAssoc($result) {
|
||||
return mysqli_fetch_assoc($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el número de filas de un resultado
|
||||
* @param mysqli_result $result Resultado de consulta
|
||||
* @return int
|
||||
*/
|
||||
function NumRows($result) {
|
||||
return mysqli_num_rows($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
function Result($result, $row, $field = 0) {
|
||||
mysqli_data_seek($result, $row);
|
||||
$row_data = mysqli_fetch_array($result);
|
||||
return is_numeric($field) ? $row_data[$field] : $row_data[$field];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el último ID insertado
|
||||
* @return int
|
||||
*/
|
||||
function InsertId() {
|
||||
return mysqli_insert_id($this->connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el número de filas afectadas
|
||||
* @return int
|
||||
*/
|
||||
function AffectedRows() {
|
||||
return mysqli_affected_rows($this->connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Libera memoria del resultado
|
||||
* @param mysqli_result $result Resultado a liberar
|
||||
*/
|
||||
function FreeResult($result) {
|
||||
mysqli_free_result($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapa caracteres especiales para prevenir SQL injection
|
||||
* @param string $string String a escapar
|
||||
* @return string
|
||||
*/
|
||||
function Escape($string) {
|
||||
return mysqli_real_escape_string($this->connection, $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cierra la conexión a la base de datos
|
||||
*/
|
||||
function Close() {
|
||||
if ($this->connection) {
|
||||
mysqli_close($this->connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el último error de MySQL
|
||||
* @return string
|
||||
*/
|
||||
function GetError() {
|
||||
return mysqli_error($this->connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicia una transacción
|
||||
*/
|
||||
function BeginTransaction() {
|
||||
mysqli_begin_transaction($this->connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirma una transacción
|
||||
*/
|
||||
function Commit() {
|
||||
mysqli_commit($this->connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revierte una transacción
|
||||
*/
|
||||
function Rollback() {
|
||||
mysqli_rollback($this->connection);
|
||||
}
|
||||
|
||||
// Destructor para asegurar cierre de conexión
|
||||
public function __destruct() {
|
||||
$this->Close();
|
||||
}
|
||||
}
|
||||
|
||||
// Ejemplo de uso (similar al original)
|
||||
/*
|
||||
$db = new DB();
|
||||
$db->Open("localhost", "usuario", "password", "basededatos");
|
||||
|
||||
$result = $db->Query("SELECT * FROM usuarios");
|
||||
while ($row = $db->FetchAssoc($result)) {
|
||||
echo $row['nombre'] . "\n";
|
||||
}
|
||||
|
||||
$db->FreeResult($result);
|
||||
*/
|
||||
257
php8-migration/ejemplo-env-config.php
Normal file
257
php8-migration/ejemplo-env-config.php
Normal file
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
/**
|
||||
* Ejemplo de configuración segura con archivo .env
|
||||
* Compatible con PHP 8 y arquitectura multi-empresa
|
||||
*/
|
||||
|
||||
// Cargar variables de entorno desde .env
|
||||
function loadEnv($path = '.env') {
|
||||
if (!file_exists($path)) {
|
||||
throw new Exception("Archivo .env no encontrado en: $path");
|
||||
}
|
||||
|
||||
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos(trim($line), '#') === 0) continue;
|
||||
|
||||
if (strpos($line, '=') !== false) {
|
||||
list($key, $value) = explode('=', $line, 2);
|
||||
$key = trim($key);
|
||||
$value = trim($value);
|
||||
|
||||
// Remover comillas si existen
|
||||
$value = trim($value, '"\'');
|
||||
|
||||
// Establecer como variable de entorno
|
||||
$_ENV[$key] = $value;
|
||||
putenv("$key=$value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cargar configuración desde .env
|
||||
try {
|
||||
loadEnv(__DIR__ . '/.env');
|
||||
} catch (Exception $e) {
|
||||
die("Error cargando configuración: " . $e->getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clase de configuración centralizada
|
||||
*/
|
||||
class Config {
|
||||
|
||||
/**
|
||||
* Obtiene configuración de base de datos por empresaId
|
||||
* @param int $empresaId ID de la empresa
|
||||
* @return array Configuración de BD para la empresa
|
||||
*/
|
||||
public static function getDatabaseConfig($empresaId) {
|
||||
// Configuración base desde .env
|
||||
$baseConfig = [
|
||||
'host' => $_ENV['DB_HOST'] ?? 'localhost',
|
||||
'port' => $_ENV['DB_PORT'] ?? '3306',
|
||||
'charset' => $_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
];
|
||||
|
||||
// Mapeo dinámico de empresas basado en estructura real del sistema
|
||||
// Patón:avantikads_nm{empresaId} donde empresaId viene del usuario
|
||||
$prefix = $_ENV['DB_EMPRESA_PREFIX'] ?? 'avantikads_nm';
|
||||
$database = $prefix . $empresaId;
|
||||
|
||||
// Validar que exista la base de datos
|
||||
if (!self::validateDatabaseExists($database)) {
|
||||
throw new Exception("Base de datos no existe para empresaId: $empresaId (DB: $database)");
|
||||
}
|
||||
|
||||
$empresas = [
|
||||
$empresaId => [
|
||||
'database' => $database,
|
||||
'user' => $_ENV['DB_EMPRESA_USER'] ?? $_ENV['DB_USER'],
|
||||
'password' => $_ENV['DB_EMPRESA_PASSWORD'] ?? $_ENV['DB_PASSWORD']
|
||||
]
|
||||
];
|
||||
|
||||
// Validar que exista configuración para la empresa
|
||||
if (!isset($empresas[$empresaId])) {
|
||||
throw new Exception("No existe configuración para empresaId: $empresaId");
|
||||
}
|
||||
|
||||
return array_merge($baseConfig, $empresas[$empresaId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida que exista la base de datos para una empresa
|
||||
* @param string $database Nombre de la base de datos
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateDatabaseExists($database) {
|
||||
try {
|
||||
$host = $_ENV['DB_HOST'] ?? 'localhost';
|
||||
$user = $_ENV['DB_USER'] ?? 'root';
|
||||
$password = $_ENV['DB_PASSWORD'] ?? '';
|
||||
|
||||
// Conexión sin especificar BD para verificar existencia
|
||||
$mysqli = new mysqli($host, $user, $password);
|
||||
|
||||
if ($mysqli->connect_error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verificar si la base de datos existe
|
||||
$result = $mysqli->query("SHOW DATABASES LIKE '$database'");
|
||||
$exists = $result->num_rows > 0;
|
||||
|
||||
$mysqli->close();
|
||||
return $exists;
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error validando BD $database: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene empresaId del usuario actual
|
||||
* @param int $userId ID del usuario
|
||||
* @return int|null empresaId del usuario
|
||||
*/
|
||||
public static function getEmpresaIdByUserId($userId) {
|
||||
// Conexión a base de datos master para obtener empresaId del usuario
|
||||
$masterConfig = [
|
||||
'host' => $_ENV['DB_MASTER_HOST'] ?? $_ENV['DB_HOST'],
|
||||
'database' => $_ENV['DB_MASTER_DATABASE'] ?? 'avantikads_nmgen',
|
||||
'user' => $_ENV['DB_MASTER_USER'] ?? $_ENV['DB_USER'],
|
||||
'password' => $_ENV['DB_MASTER_PASSWORD'] ?? $_ENV['DB_PASSWORD']
|
||||
];
|
||||
|
||||
try {
|
||||
$mysqli = new mysqli(
|
||||
$masterConfig['host'],
|
||||
$masterConfig['user'],
|
||||
$masterConfig['password'],
|
||||
$masterConfig['database']
|
||||
);
|
||||
|
||||
if ($mysqli->connect_error) {
|
||||
throw new Exception("Error conexión master DB: " . $mysqli->connect_error);
|
||||
}
|
||||
|
||||
$stmt = $mysqli->prepare("SELECT empresaId FROM usuario WHERE usuarioId = ? LIMIT 1");
|
||||
$stmt->bind_param("i", $userId);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
if ($row = $result->fetch_assoc()) {
|
||||
return (int)$row['empresaId'];
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error obteniendo empresaId: " . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene configuración general del sistema
|
||||
* @return array Configuración general
|
||||
*/
|
||||
public static function getSystemConfig() {
|
||||
return [
|
||||
'doc_root' => $_ENV['DOC_ROOT'] ?? '/var/www/html/ventas',
|
||||
'web_root' => $_ENV['WEB_ROOT'] ?? 'http://localhost',
|
||||
'smtp_host' => $_ENV['SMTP_HOST'] ?? '',
|
||||
'smtp_user' => $_ENV['SMTP_USER'] ?? '',
|
||||
'smtp_port' => $_ENV['SMTP_PORT'] ?? '587',
|
||||
'items_per_page' => $_ENV['ITEMS_PER_PAGE'] ?? '20',
|
||||
'min_year' => $_ENV['MIN_YEAR'] ?? '2025',
|
||||
'max_year' => $_ENV['MAX_YEAR'] ?? '2030',
|
||||
'debug_mode' => $_ENV['DEBUG_MODE'] ?? 'false'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clase de base de datos mejorada con soporte multi-empresa
|
||||
*/
|
||||
class DatabaseManager {
|
||||
private $connections = [];
|
||||
private $currentEmpresaId = null;
|
||||
|
||||
/**
|
||||
* Obtiene conexión para empresa específica
|
||||
* @param int $empresaId ID de la empresa
|
||||
* @return mysqli
|
||||
*/
|
||||
public function getConnection($empresaId) {
|
||||
if (!isset($this->connections[$empresaId])) {
|
||||
$config = Config::getDatabaseConfig($empresaId);
|
||||
|
||||
$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 = Config::getEmpresaIdByUserId($userId);
|
||||
|
||||
if (!$this->currentEmpresaId) {
|
||||
throw new Exception("No se pudo determinar empresaId para usuario: $userId");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene conexión para empresa actual
|
||||
* @return mysqli
|
||||
*/
|
||||
public function getCurrentConnection() {
|
||||
if (!$this->currentEmpresaId) {
|
||||
throw new Exception("No hay empresaId establecido");
|
||||
}
|
||||
|
||||
return $this->getConnection($this->currentEmpresaId);
|
||||
}
|
||||
}
|
||||
|
||||
// Ejemplo de inicialización
|
||||
/*
|
||||
try {
|
||||
// Cargar configuración
|
||||
$dbManager = new DatabaseManager();
|
||||
|
||||
// Establecer empresa basado en usuario en sesión
|
||||
if (isset($_SESSION['userId'])) {
|
||||
$dbManager->setEmpresaByUser($_SESSION['userId']);
|
||||
}
|
||||
|
||||
// Obtener conexión para la empresa del usuario
|
||||
$db = $dbManager->getCurrentConnection();
|
||||
|
||||
// Ejecutar consulta
|
||||
$result = $db->query("SELECT * FROM productos");
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error en inicialización: " . $e->getMessage());
|
||||
die("Error del sistema");
|
||||
}
|
||||
*/
|
||||
?>
|
||||
120
php8-migration/plan-ejecucion.md
Normal file
120
php8-migration/plan-ejecucion.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Plan de Migración PHP 8 - Archivos y Cambios Específicos
|
||||
|
||||
## Requisitos Adicionales del Cliente
|
||||
|
||||
### Configuración Segura con .env
|
||||
- **Migrar toda configuración sensible a archivo .env**
|
||||
- **Eliminar credenciales de bases de datos del código**
|
||||
- **Usar empresaId del usuario para determinar conexión**
|
||||
|
||||
### Arquitectura Multi-Empresa
|
||||
- **Los usuarios tienen campo empresaId en su registro**
|
||||
- **La conexión a BD debe seleccionarse dinámicamente por empresaId**
|
||||
- **No debe existir información de bases de datos fuera del .env**
|
||||
|
||||
## Archivos Creados para la Migración
|
||||
|
||||
### 1. `/php8-migration/analisis-sistema.md`
|
||||
- Análisis general del sistema
|
||||
- Identificación de estructura y componentes
|
||||
- Evaluación de librerías utilizadas
|
||||
|
||||
### 2. `/php8-migration/reporte-problemas.md`
|
||||
- Reporte detallado de incompatibilidades
|
||||
- Priorización de problemas
|
||||
- Estimación de tiempos y riesgos
|
||||
|
||||
### 3. `/php8-migration/ejemplo-db-mysqli.php`
|
||||
- Ejemplo de migración de MySQL a MySQLi
|
||||
- Código compatible con PHP 8
|
||||
|
||||
### 4. `/php8-migration/ejemplo-env-config.php`
|
||||
- Ejemplo de configuración con archivo .env
|
||||
- Gestión multi-empresa basada en usuario
|
||||
|
||||
## Cambios Críticos Requeridos
|
||||
|
||||
### Nivel URGENTE - Configuración y Base de Datos
|
||||
|
||||
#### 1. Migrar a Configuración .env
|
||||
**Archivo:** `config.php` (eliminar completamente)
|
||||
- **Eliminar todas las credenciales del código**
|
||||
- **Crear archivo .env con variables seguras**
|
||||
- **Implementar clase Config para gestión centralizada**
|
||||
- **Impacto:** Seguridad y mantenibilidad
|
||||
|
||||
#### 2. Implementar Conexión Multi-Empresa
|
||||
**Archivos:** `classes/db.class.php`, `classes/util.class.php`
|
||||
- **Reemplazar 13 funciones mysql_* por mysqli***
|
||||
- **Implementar gestión de empresas basada en empresaId del usuario**
|
||||
- **Crear DatabaseManager para conexiones dinámicas**
|
||||
- **Impacto:** Sistema completo sin base de datos
|
||||
|
||||
#### 3. Actualizar Lógica de Selección de BD
|
||||
**Archivos:** 100+ archivos con `$_SESSION['empresaId']`
|
||||
- **Reemplazar `SQL_DATABASE2.$empresaId` por conexión dinámica**
|
||||
- **Usar DatabaseManager para obtener conexión específica**
|
||||
- **Impacto:** Funcionalidad multi-empresa completa
|
||||
|
||||
### Nivel ALTO - Funciones Eliminadas
|
||||
1. **`classes/util.class.php:575`** - `ereg_replace()` → `preg_replace()`
|
||||
2. **`ajax/cuentas-pagar.php:18`** - `split()` → `explode()`
|
||||
3. **`ajax/evaluar-pedidos.php:160,360`** - `split()` → `explode()`
|
||||
4. **7 archivos con `each()`** - Reemplazar por `foreach`
|
||||
5. **4 archivos con `create_function()`** - Reemplazar por funciones anónimas
|
||||
|
||||
### Nivel MEDIO - Sintaxis
|
||||
1. **100+ ocurrencias de `$string{index}`** → `$string[index]`
|
||||
2. **98+ ocurrencias de `list()` incorrecto** → sintaxis moderna
|
||||
3. **Múltiples asignaciones por referencia** → revisión y limpieza
|
||||
|
||||
## Estructura de Archivos de Migración
|
||||
|
||||
```
|
||||
php8-migration/
|
||||
├── analisis-sistema.md # Análisis general
|
||||
├── reporte-problemas.md # Problemas identificados
|
||||
├── ejemplo-db-mysqli.php # Ejemplo de migración DB
|
||||
├── ejemplo-env-config.php # Ejemplo configuración .env
|
||||
├── .env.example # Plantilla archivo .env
|
||||
├── plan-ejecucion.md # Plan actualizado (este archivo)
|
||||
└── archivos-criticos.txt # Lista de archivos críticos
|
||||
```
|
||||
|
||||
## Recomendaciones de Seguridad
|
||||
|
||||
### Antes de Cualquier Cambio:
|
||||
1. ✅ **Backup completo del sistema**
|
||||
2. ✅ **Crear entorno de prueba aislado**
|
||||
3. ✅ **Documentar versión actual funcional**
|
||||
4. ✅ **Lista de todos los usuarios y procesos activos**
|
||||
|
||||
### Durante la Migración:
|
||||
1. ✅ **Cambiar un módulo a la vez**
|
||||
2. ✅ **Probar cada cambio inmediatamente**
|
||||
3. ✅ **Mantener log detallado de modificaciones**
|
||||
4. ✅ **No aplicar cambios a producción sin pruebas**
|
||||
|
||||
### Después de Cada Cambio:
|
||||
1. ✅ **Ejecutar pruebas unitarias**
|
||||
2. ✅ **Verificar funcionalidad completa**
|
||||
3. ✅ **Comprobar rendimiento**
|
||||
4. ✅ **Validar que no hay regresiones**
|
||||
|
||||
## Próximos Pasos Sugeridos
|
||||
|
||||
1. **Aprobación del plan:** Revisar y aprobar este análisis
|
||||
2. **Preparación del entorno:** Configurar entorno de pruebas
|
||||
3. **Backup completo:** Realizar backup integral del sistema
|
||||
4. **Migración fase 1:** Actualizar capa de base de datos (MySQL → MySQLi)
|
||||
5. **Migración fase 2:** Reemplazar funciones eliminadas
|
||||
6. **Migración fase 3:** Actualizar sintaxis
|
||||
7. **Pruebas finales:** Validación completa del sistema
|
||||
|
||||
## Estado Actual
|
||||
- ❌ **NO COMPATIBLE** con PHP 8
|
||||
- ⚠️ **Requiere migración completa** antes de actualizar PHP
|
||||
- 🔴 **Alto riesgo** de pérdida total de funcionalidad
|
||||
- 📋 **Plan detallado** disponible para ejecución
|
||||
|
||||
**ADVERTENCIA:** No actualizar la versión de PHP sin completar todos los cambios de migración.
|
||||
109
php8-migration/reporte-problemas.md
Normal file
109
php8-migration/reporte-problemas.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Reporte Detallado de Problemas de Compatibilidad con PHP 8
|
||||
|
||||
## Resumen Ejecutivo
|
||||
El sistema actual **NO es compatible con PHP 8** debido a múltiples problemas críticos que requieren actualización inmediata.
|
||||
|
||||
## Problemas Críticos Identificados
|
||||
|
||||
### 1. Funciones MySQL Obsoletas (Nivel CRÍTICO)
|
||||
**Archivo:** `classes/db.class.php`
|
||||
- **Líneas afectadas:** 87, 88, 103, 107, 117, 133, 147, 154, 165, 178, 189, 203, 212
|
||||
- **Funciones a reemplazar:**
|
||||
- `mysql_connect()` → `mysqli_connect()` o `PDO::__construct()`
|
||||
- `mysql_select_db()` → `mysqli_select_db()` o parte de PDO DSN
|
||||
- `mysql_query()` → `mysqli_query()` o `PDO::query()`
|
||||
- `mysql_fetch_assoc()` → `mysqli_fetch_assoc()` o `PDO::fetch()`
|
||||
- `mysql_num_rows()` → `mysqli_num_rows()` o método PDO equivalente
|
||||
- `mysql_result()` → `mysqli_data_seek()` + `mysqli_fetch_row()`
|
||||
- `mysql_insert_id()` → `mysqli_insert_id()` o `PDO::lastInsertId()`
|
||||
- `mysql_affected_rows()` → `mysqli_affected_rows()`
|
||||
- `mysql_free_result()` → `mysqli_free_result()`
|
||||
- `mysql_fetch_array()` → `mysqli_fetch_array()`
|
||||
|
||||
### 2. Funciones Eliminadas en PHP 8 (Nivel ALTO)
|
||||
|
||||
#### ereg_replace()
|
||||
**Archivo:** `classes/util.class.php:575`
|
||||
- **Reemplazo:** `preg_replace('/patron/', 'reemplazo', $string)`
|
||||
|
||||
#### each() function
|
||||
**Archivos afectados (7 total):**
|
||||
- `pdf/include/style.cls.php:790`
|
||||
- `pdf/fpdf.php:1565`
|
||||
- `pdf/fpdi.php:435`
|
||||
- `classes/class.phpmailer.php:1645`
|
||||
- `classes/class.smtp.php:388,417`
|
||||
- `classes/comprobante.class.php:1465`
|
||||
- **Reemplazo:** Estructuras `foreach` o `reset() + key() + current()`
|
||||
|
||||
#### create_function()
|
||||
**Archivos afectados (4 total):**
|
||||
- `pdf/include/frame_reflower.cls.php:233`
|
||||
- `pdf/include/text_frame_reflower.cls.php:380,388,416`
|
||||
- **Reemplazo:** Funciones anónimas o closures
|
||||
|
||||
#### split() function
|
||||
**Archivos afectados:**
|
||||
- `pdf/domdf.php:227`
|
||||
- `classes/comprobante.class.php:350`
|
||||
- `ajax/evaluar-pedidos.php:160,360`
|
||||
- `ajax/cuentas-pagar.php:18`
|
||||
- **Reemplazo:** `explode()` (para separadores simples) o `preg_split()` (para expresiones regulares)
|
||||
|
||||
### 3. Sintaxis Incompatible (Nivel MEDIO)
|
||||
|
||||
#### Llaves para acceso a strings/arrays
|
||||
**Patrón:** `$string{index}` → `$string[index]`
|
||||
**Archivos con más incidencias:**
|
||||
- `tcpdf/barcodes.php` (50+ ocurrencias)
|
||||
- `classes/json.class.php` (20+ ocurrencias)
|
||||
- Múltiples archivos en tcpdf/, pdf/, libs/
|
||||
|
||||
#### Asignaciones list() con elementos vacíos
|
||||
**Ejemplos problemáticos:**
|
||||
```php
|
||||
// Incompatible
|
||||
list($x1,,$x2) = $array;
|
||||
list(,$y2,,,,$y1) = $array;
|
||||
|
||||
// Compatible
|
||||
list($x1, $_, $x2) = $array;
|
||||
list($_, $y2, $_, $_, $_, $y1) = $array;
|
||||
// O mejor aún: usar array_slice() o destructuring moderno
|
||||
```
|
||||
|
||||
## Plan de Acción Priorizado
|
||||
|
||||
### Fase 1: Urgente (Funciones MySQL)
|
||||
1. **Crear nueva clase de base de datos compatible con mysqli/PDO**
|
||||
2. **Actualizar todas las llamadas a funciones mysql_***
|
||||
3. **Probar exhaustivamente la conexión y consultas**
|
||||
|
||||
### Fase 2: Funciones Eliminadas
|
||||
1. **Reemplazar ereg_replace() → preg_replace()**
|
||||
2. **Convertir each() → foreach()**
|
||||
3. **Actualizar create_function() → funciones anónimas**
|
||||
4. **Cambiar split() → explode() / preg_split()**
|
||||
|
||||
### Fase 3: Sintaxis
|
||||
1. **Actualizar sintaxis de llaves**
|
||||
2. **Corregir asignaciones list()**
|
||||
3. **Revisar asignaciones por referencia**
|
||||
|
||||
## Estimación de Tiempo
|
||||
- **Fase 1 (MySQL):** 20-30 horas
|
||||
- **Fase 2 (Funciones):** 15-20 horas
|
||||
- **Fase 3 (Sintaxis):** 10-15 horas
|
||||
- **Pruebas:** 15-20 horas
|
||||
- **Total:** 60-85 horas
|
||||
|
||||
## Riesgos
|
||||
- **Alto:** Corrupción de datos durante migración MySQL
|
||||
- **Medio:** Pérdida de funcionalidad en reportes PDF
|
||||
- **Bajo:** Problemas de rendimiento temporales
|
||||
|
||||
## Recomendaciones
|
||||
1. **NO migrar sin actualizar completamente**
|
||||
2. **Crear entorno de prueba aislado**
|
||||
3. **Backup completo antes de cambios**
|
||||
4. **Migración incremental por módulos**
|
||||
Reference in New Issue
Block a user