diff --git a/ci4/app/Commands/CatalogoLibroAsignarIskn.php b/ci4/app/Commands/CatalogoLibroAsignarIskn.php
index 75a7b9a2..d7472678 100644
--- a/ci4/app/Commands/CatalogoLibroAsignarIskn.php
+++ b/ci4/app/Commands/CatalogoLibroAsignarIskn.php
@@ -9,7 +9,7 @@ use App\Models\Catalogo\IdentificadorIsknModel;
class CatalogoLibroAsignarIskn extends BaseCommand
{
- protected $group = 'custom';
+ protected $group = 'Safekat';
protected $name = 'catalogo:libro-asignar-iskn';
protected $description = 'Asigna ISKN directamente en la base de datos a los libros que no lo tienen.';
diff --git a/ci4/app/Commands/CatalogoLibroImportar.php b/ci4/app/Commands/CatalogoLibroImportar.php
index e6beb331..6feadc1e 100644
--- a/ci4/app/Commands/CatalogoLibroImportar.php
+++ b/ci4/app/Commands/CatalogoLibroImportar.php
@@ -7,7 +7,7 @@ use CodeIgniter\CLI\CLI;
class CatalogoLibroImportar extends BaseCommand
{
- protected $group = 'custom';
+ protected $group = 'Safekat';
protected $name = 'catalogo:libro-importar';
protected $description = 'Importa los registros de catalogo_libro a catalogo_libros para un customer_id dado';
diff --git a/ci4/app/Commands/RestoreBackup.php b/ci4/app/Commands/RestoreBackup.php
new file mode 100644
index 00000000..1b3a9839
--- /dev/null
+++ b/ci4/app/Commands/RestoreBackup.php
@@ -0,0 +1,93 @@
+ 'Simula el proceso de restauración sin ejecutarlo realmente.',
+ ];
+
+ public function run(array $params)
+ {
+ $isDryRun = CLI::getOption('dry-run');
+
+ $backupDir = WRITEPATH . 'backups/';
+ $backups = glob($backupDir . '*.zip');
+
+ if (empty($backups)) {
+ CLI::error("No se encontraron backups .zip en: $backupDir");
+ return;
+ }
+
+ CLI::write("Backups disponibles:", 'blue');
+ foreach ($backups as $i => $file) {
+ CLI::write("[" . ($i + 1) . "] " . basename($file));
+ }
+
+ $index = CLI::prompt("Selecciona el número del backup a restaurar", null, 'required');
+
+ if (!is_numeric($index) || $index < 1 || $index > count($backups)) {
+ CLI::error("Selección no válida.");
+ return;
+ }
+
+ $selectedFile = $backups[$index - 1];
+ CLI::write("🎯 Seleccionado: " . basename($selectedFile), 'cyan');
+
+ if ($isDryRun) {
+ CLI::write("🔍 Modo simulación activado (--dry-run)", 'yellow');
+ }
+
+ $zip = new ZipArchive();
+ if ($zip->open($selectedFile) !== TRUE) {
+ CLI::error("No se pudo abrir el archivo ZIP.");
+ return;
+ }
+
+ $extractPath = WRITEPATH . 'backups/tmp_restore/';
+ if (!is_dir($extractPath)) {
+ mkdir($extractPath, 0775, true);
+ }
+
+ $zip->extractTo($extractPath);
+ $zip->close();
+
+ $sqlFiles = glob($extractPath . '*.sql');
+ if (count($sqlFiles) === 0) {
+ CLI::error("No se encontró ningún .sql dentro del backup.");
+ return;
+ }
+
+ $sqlFile = escapeshellarg($sqlFiles[0]);
+ $db = config('Database')->default;
+
+ $cmd = "mysql -h {$db['hostname']} -u {$db['username']} -p'{$db['password']}' {$db['database']} < {$sqlFile}";
+
+ if ($isDryRun) {
+ CLI::write("📋 Comando que se ejecutaría:", 'yellow');
+ CLI::write($cmd, 'light_gray');
+ CLI::write("✅ Simulación completa. No se hizo ninguna modificación.", 'green');
+ } else {
+ CLI::write("⏳ Restaurando...", 'yellow');
+ system($cmd, $retval);
+
+ if ($retval !== 0) {
+ CLI::error("❌ Error al restaurar la base de datos (código $retval).");
+ } else {
+ CLI::write("✅ Backup restaurado correctamente.", 'green');
+ }
+ }
+
+ array_map('unlink', glob($extractPath . '*'));
+ rmdir($extractPath);
+ }
+}
diff --git a/ci4/app/Config/Routes/SistemaRoutes.php b/ci4/app/Config/Routes/SistemaRoutes.php
index d0d0559d..7bb095cf 100644
--- a/ci4/app/Config/Routes/SistemaRoutes.php
+++ b/ci4/app/Config/Routes/SistemaRoutes.php
@@ -17,17 +17,19 @@ $routes->group('sistema', ['namespace' => 'App\Controllers\Sistema'], function (
});
/* Backups */
- $routes->group('backups', ['namespace' => 'App\Controllers\Sistema'], function ($routes) {
+ $routes->group('backups', function ($routes) {
/**======================
* Tool
*========================**/
$routes->get('', 'Backups::index', ['as' => 'backupsList']);
$routes->get('create', 'Backups::create', ['as' => 'backupsCreate']);
+ $routes->get('create-dev', 'Backups::createDevelopment', ['as' => 'backupsCreateDev']);
+ $routes->get('backups/download/(:segment)', 'Backups::download/$1', ['as' => 'backupsDownload']);
$routes->get('delete-local/(:num)', 'Backups::deleteLocal/$1', ['as' => 'backupsDeleteLocal']);
+ $routes->get('delete-local-dev/(:segment)', 'Backups::deleteLocalDevelopment/$1', ['as' => 'backupsDeleteLocalDev']);
$routes->get('restore/(:segment)/local', 'Backups::restoreLocal/$1', ['as' => 'backupsRestoreLocal']);
$routes->get('restore/(:segment)/remote', 'Backups::restoreRemote/$1', ['as' => 'backupsRestoreRemote']);
-
});
});
\ No newline at end of file
diff --git a/ci4/app/Controllers/Sistema/Backups.php b/ci4/app/Controllers/Sistema/Backups.php
index 40ef0b54..84538b10 100644
--- a/ci4/app/Controllers/Sistema/Backups.php
+++ b/ci4/app/Controllers/Sistema/Backups.php
@@ -19,51 +19,141 @@ class Backups extends BaseController
{
helper('filesystem');
- $entries = $this->backupModel->orderBy('created_at', 'DESC')->findAll();
-
+ $entorno = getenv('SK_ENVIRONMENT');
$backups = [];
- foreach ($entries as $entry) {
- $file = $entry['filename'];
- $localPath = $entry['path_local'];
- $remotePath = $entry['path_remote'];
- $isLocal = $localPath && file_exists($localPath);
- $isRemote = !empty($remotePath);
+ if ($entorno === 'development') {
+ // Leer archivos directamente del disco en entorno de desarrollo
+ $backups = [];
- if ($isLocal) {
- $fecha = date('Y-m-d H:i', filemtime($localPath));
- $tamano = filesize($localPath);
- $tamanoFmt = number_format($tamano / 1024, 2) . ' KB';
+ // === 1. Backups locales ===
+ $localDir = WRITEPATH . 'backups/';
+ $localFiles = get_filenames($localDir);
- // Actualizar la BD si ha cambiado
- if ($entry['size'] != $tamano) {
- $this->backupModel->update($entry['id'], [
- 'size' => $tamano,
- ]);
+ foreach ($localFiles as $file) {
+ if (!str_ends_with($file, '.zip') || !str_starts_with($file, 'backup_dev_')) {
+ continue;
}
- } else {
- $fecha = $entry['created_at'] ?? '-';
- $tamano = $entry['size'] ?? null;
- $tamanoFmt = $tamano ? number_format($tamano / 1024, 2) . ' KB' : '-';
+ $localPath = $localDir . $file;
+
+ $fecha = date('Y-m-d H:i', filemtime($localPath));
+ $tamano = filesize($localPath);
+ $tamanoFmt = $tamano > 1048576
+ ? number_format($tamano / 1048576, 2) . ' MB'
+ : number_format($tamano / 1024, 2) . ' KB';
+
+ $backups[$file] = [
+ 'id' => null,
+ 'filename' => $file,
+ 'fecha' => $fecha,
+ 'tamano' => $tamanoFmt,
+ 'local' => true,
+ 'remoto' => false,
+ ];
}
- $backups[] = [
- 'id' => $entry['id'],
- 'filename' => $file,
- 'fecha' => $fecha,
- 'tamano' => $tamanoFmt,
- 'local' => $isLocal,
- 'remoto' => $isRemote,
- ];
+ // === 2. Backups remotos en SFTP ===
+ $sftpHost = getenv('HIDRIVE_HOST');
+ $sftpUser = getenv('HIDRIVE_USER');
+ $sftpPass = getenv('HIDRIVE_PASS');
+ $remoteDir = '/users/erp2019/backups_erp/';
+
+ $sftp = new SFTP($sftpHost);
+ if ($sftp->login($sftpUser, $sftpPass)) {
+ $remoteFiles = $sftp->nlist($remoteDir);
+
+ foreach ($remoteFiles as $file) {
+ if (!str_ends_with($file, '.zip') || !str_starts_with($file, 'backup_')) {
+ continue;
+ }
+
+ // Verificar si ya se cargó como local
+ $alreadyLocal = isset($backups[$file]);
+
+ // Obtener info de archivo remoto
+ $stat = $sftp->stat($remoteDir . $file);
+ $fecha = isset($stat['mtime']) ? date('Y-m-d H:i', $stat['mtime']) : '-';
+ $tamano = $stat['size'] ?? null;
+ $tamanoFmt = $tamano > 1048576
+ ? number_format($tamano / 1048576, 2) . ' MB'
+ : number_format($tamano / 1024, 2) . ' KB';
+
+ $backups[$file] = [
+ 'id' => null,
+ 'filename' => $file,
+ 'fecha' => $fecha,
+ 'tamano' => $tamanoFmt,
+ 'local' => $alreadyLocal,
+ 'remoto' => true,
+ ];
+ }
+ } else {
+ // Opcional: mostrar un error o dejarlo pasar silenciosamente
+ session()->setFlashdata('warning', 'No se pudo conectar al servidor SFTP para obtener backups remotos.');
+ }
+
+ // Convertir a lista (por si se usó índice por filename)
+ $backups = array_values($backups);
+
+ } else {
+ // En producción: seguir usando la base de datos
+ $entries = $this->backupModel->orderBy('created_at', 'DESC')->findAll();
+
+ foreach ($entries as $entry) {
+ $file = $entry['filename'];
+
+ if (!str_ends_with($file, '.zip')) {
+ continue;
+ }
+
+ $localPath = $entry['path_local'];
+ $remotePath = $entry['path_remote'];
+
+ $isLocal = $localPath && file_exists($localPath);
+ $isRemote = !empty($remotePath);
+
+ if ($isLocal) {
+ $fecha = date('Y-m-d H:i', filemtime($localPath));
+ $tamano = filesize($localPath);
+ $tamanoFmt = $tamano > 1048576
+ ? number_format($tamano / 1048576, 2) . ' MB'
+ : number_format($tamano / 1024, 2) . ' KB';
+
+ if ($entry['size'] != $tamano) {
+ $this->backupModel->update($entry['id'], [
+ 'size' => $tamano,
+ ]);
+ }
+
+ } else {
+ $fecha = $entry['created_at'] ?? '-';
+ $tamano = $entry['size'] ?? null;
+ $tamanoFmt = $tamano > 1048576
+ ? number_format($tamano / 1048576, 2) . ' MB'
+ : number_format($tamano / 1024, 2) . ' KB';
+ }
+
+ $backups[] = [
+ 'id' => $entry['id'],
+ 'filename' => $file,
+ 'fecha' => $fecha,
+ 'tamano' => $tamanoFmt,
+ 'local' => $isLocal,
+ 'remoto' => $isRemote,
+ ];
+ }
}
return view('themes/vuexy/form/backups/backupList', ['backups' => $backups]);
}
-
public function create()
{
+ if (getenv('SK_ENVIRONMENT') !== 'production') {
+ return redirect()->to(route_to('backupsList'))->with('error', 'No permitido en entorno de desarrollo.');
+ }
+
helper('filesystem');
$timestamp = date('Ymd_His');
@@ -78,7 +168,8 @@ class Backups extends BaseController
$password = $dbConfig['password'];
$database = $dbConfig['database'];
- $command = "mysqldump -h {$host} -u{$username} -p'{$password}' {$database} > {$sqlPath}";
+ $command = "mysqldump -h {$host} -u" . escapeshellarg($username) . " -p'" . addslashes($password) . "' {$database} > {$sqlPath}";
+
system($command, $retval);
if ($retval !== 0) {
@@ -116,9 +207,175 @@ class Backups extends BaseController
'status' => 'subido'
]);
- return redirect()->to(route_to('backupsList'))->with('message', 'Backup creado, comprimido y enviado.');
+ return redirect()->to(route_to('backupsList'))->with('message', 'Backup del entorno de produccion creado, comprimido y enviado al remoto.');
}
+ public function createDevelopment()
+ {
+ if (getenv('SK_ENVIRONMENT') !== 'development') {
+ return redirect()->to(route_to('backupsList'))->with('error', 'Esta acción solo está permitida en desarrollo.');
+ }
+
+ helper('filesystem');
+
+ $timestamp = date('Ymd_His');
+ $sqlFilename = "backup_dev_{$timestamp}.sql";
+ $zipFilename = "backup_dev_{$timestamp}.zip";
+ $sqlPath = WRITEPATH . 'backups/' . $sqlFilename;
+ $zipPath = WRITEPATH . 'backups/' . $zipFilename;
+
+ $dbConfig = config('Database')->default;
+ $host = $dbConfig['hostname'];
+ $username = $dbConfig['username'];
+ $password = $dbConfig['password'];
+ $database = $dbConfig['database'];
+
+ $command = "mysqldump -h {$host} -u{$username} -p'{$password}' {$database} > {$sqlPath}";
+ system($command, $retval);
+
+ if ($retval !== 0) {
+ throw new \RuntimeException("Error al crear el backup local.");
+ }
+
+ $zip = new \ZipArchive();
+ if ($zip->open($zipPath, \ZipArchive::CREATE) === TRUE) {
+ $zip->addFile($sqlPath, $sqlFilename);
+ $zip->close();
+ unlink($sqlPath);
+ } else {
+ throw new \RuntimeException("Error al comprimir el backup local.");
+ }
+
+ // Ya no insertamos en la base de datos; el archivo queda en disco y se listará dinámicamente
+ return redirect()->to(route_to('backupsList'))->with('message', 'Backup local del entorno de desarrollo creado.');
+ }
+
+
+ public function download($filename)
+ {
+ helper('filesystem');
+
+ $entorno = getenv('SK_ENVIRONMENT');
+
+ $backup = $this->backupModel->where('filename', $filename)->first();
+
+ // 1. Si hay entrada en la base de datos
+ if ($backup) {
+ $localPath = $backup['path_local'];
+ $remotePath = $backup['path_remote'];
+
+ if ($localPath && file_exists($localPath)) {
+ return $this->response->download($localPath, null)->setFileName($filename);
+ }
+
+ if (!empty($remotePath)) {
+ $sftpHost = getenv('HIDRIVE_HOST');
+ $sftpUser = getenv('HIDRIVE_USER');
+ $sftpPass = getenv('HIDRIVE_PASS');
+
+ $sftp = new SFTP($sftpHost);
+
+ if (!$sftp->login($sftpUser, $sftpPass)) {
+ return redirect()->to(route_to('backupsList'))->with('error', 'No se pudo conectar al servidor SFTP.');
+ }
+
+ $fileContents = $sftp->get($remotePath);
+ if ($fileContents === false) {
+ return redirect()->to(route_to('backupsList'))->with('error', 'Error al descargar desde SFTP.');
+ }
+
+ $newLocalPath = WRITEPATH . 'backups/' . $filename;
+ write_file($newLocalPath, $fileContents);
+
+ // Omitimos update() si estamos en desarrollo sin base de datos
+ if ($entorno === 'production') {
+ $this->backupModel->update($backup['id'], ['path_local' => $newLocalPath]);
+ }
+
+ return $this->response->download($newLocalPath, null)->setFileName($filename);
+ }
+
+ return redirect()->to(route_to('backupsList'))->with('error', 'No se pudo encontrar el archivo ni local ni remoto.');
+ }
+
+ // 2. Si NO hay entrada en la BD y estamos en desarrollo
+ if ($entorno === 'development') {
+ $localPath = WRITEPATH . 'backups/' . $filename;
+
+ if (file_exists($localPath)) {
+ return $this->response->download($localPath, null)->setFileName($filename);
+ }
+
+ // También se puede intentar buscar en el SFTP si quieres
+ $sftpHost = getenv('HIDRIVE_HOST');
+ $sftpUser = getenv('HIDRIVE_USER');
+ $sftpPass = getenv('HIDRIVE_PASS');
+ $remotePath = '/users/erp2019/backups_erp/' . $filename;
+
+ $sftp = new SFTP($sftpHost);
+ if ($sftp->login($sftpUser, $sftpPass)) {
+ $fileContents = $sftp->get($remotePath);
+ if ($fileContents !== false) {
+ $newLocalPath = WRITEPATH . 'backups/' . $filename;
+ write_file($newLocalPath, $fileContents);
+ return $this->response->download($newLocalPath, null)->setFileName($filename);
+ }
+ }
+
+ return redirect()->to(route_to('backupsList'))->with('error', 'Archivo no encontrado local ni remoto (sin base de datos).');
+ }
+
+ return redirect()->to(route_to('backupsList'))->with('error', 'Backup no encontrado.');
+ }
+
+
+
+ public function deleteLocal($id)
+ {
+ $entorno = getenv('SK_ENVIRONMENT');
+ $backup = $this->backupModel->find($id);
+
+ if (!$backup) {
+ return redirect()->to(route_to('backupsList'))->with('error', 'Backup no encontrado en la base de datos.');
+ }
+
+ $localPath = $backup['path_local'];
+
+ // Si existe el archivo, intentamos borrarlo
+ if ($localPath && file_exists($localPath)) {
+ unlink($localPath);
+ }
+
+ if ($entorno === 'production') {
+ // Solo desvincular el archivo local
+ $this->backupModel->update($id, ['path_local' => null]);
+ return redirect()->to(route_to('backupsList'))->with('message', 'Archivo local eliminado (registro conservado).');
+ } else {
+ // Eliminar completamente en desarrollo
+ $this->backupModel->delete($id);
+ return redirect()->to(route_to('backupsList'))->with('message', 'Backup de desarrollo eliminado completamente.');
+ }
+ }
+
+ public function deleteLocalDevelopment($filename)
+ {
+ $entorno = getenv('SK_ENVIRONMENT');
+ if ($entorno !== 'development') {
+ return redirect()->to(route_to('backupsList'))->with('error', 'Solo permitido en desarrollo.');
+ }
+
+ $path = WRITEPATH . 'backups/' . $filename;
+
+ if (file_exists($path)) {
+ unlink($path);
+ return redirect()->to(route_to('backupsList'))->with('message', "Archivo '$filename' eliminado del sistema de archivos.");
+ }
+
+ return redirect()->to(route_to('backupsList'))->with('error', "Archivo '$filename' no encontrado en el sistema.");
+ }
+
+
+
public function restoreLocal($file)
{
$path = WRITEPATH . 'backups/' . $file;
@@ -126,7 +383,7 @@ class Backups extends BaseController
throw new \CodeIgniter\Exceptions\PageNotFoundException("Backup no encontrado.");
}
- $zip = new ZipArchive();
+ $zip = new \ZipArchive();
if ($zip->open($path) === TRUE) {
$extractPath = WRITEPATH . 'backups/tmp_restore/';
if (!is_dir($extractPath)) {
@@ -140,31 +397,32 @@ class Backups extends BaseController
throw new \RuntimeException("No se encontró ningún archivo .sql en el ZIP");
}
- $db = \Config\Database::connect();
- $sql = file_get_contents($sqlFiles[0]);
- $db->query('SET FOREIGN_KEY_CHECKS=0;');
- $db->query($sql);
- $db->query('SET FOREIGN_KEY_CHECKS=1;');
+ $sqlFile = $sqlFiles[0];
+
+ $dbConfig = config('Database')->default;
+ $host = $dbConfig['hostname'];
+ $username = $dbConfig['username'];
+ $password = $dbConfig['password'];
+ $database = $dbConfig['database'];
+
+ $cmd = "mysql -h {$host} -u{$username} -p'{$password}' {$database} < {$sqlFile}";
+ system($cmd, $retval);
+
+ if ($retval !== 0) {
+ throw new \RuntimeException("Error al restaurar la base de datos. Código: $retval");
+ }
array_map('unlink', glob($extractPath . '*'));
rmdir($extractPath);
- return redirect()->to('/backups')->with('message', 'Backup restaurado correctamente.');
+ return redirect()->to(route_to('backupsList'))->with('message', 'Backup restaurado correctamente (vía sistema).');
} else {
throw new \RuntimeException("No se pudo abrir el archivo ZIP");
}
}
- public function deleteLocal($id)
- {
- $backup = $this->backupModel->find($id);
- if ($backup && $backup['path_local'] && file_exists($backup['path_local'])) {
- unlink($backup['path_local']);
- $this->backupModel->update($id, ['path_local' => null]);
- return redirect()->to(route_to('backupsList'))->with('message', 'Backup local eliminado.');
- }
- return redirect()->to(route_to('backupsList'))->with('error', 'Archivo no encontrado.');
- }
+
+
public function restoreRemote($filename)
{
@@ -178,9 +436,9 @@ class Backups extends BaseController
}
// Parámetros SFTP
- $sftpHost = 'sftp.hidrive.ionos.com';
- $sftpUser = 'erp2019';
- $sftpPass = 'Z2CjX7kd2h';
+ $sftpHost = getenv('HIDRIVE_HOST');
+ $sftpUser = getenv('HIDRIVE_USER');
+ $sftpPass = getenv('HIDRIVE_PASS');
$remotePath = $backup['path_remote'];
$localPath = WRITEPATH . 'backups/' . $filename;
@@ -216,9 +474,9 @@ class Backups extends BaseController
private function sendToSFTP($localPath, $remoteFilename)
{
- $sftpHost = 'sftp.hidrive.ionos.com';
- $sftpUser = 'erp2019';
- $sftpPass = 'Z2CjX7kd2h';
+ $sftpHost = getenv('HIDRIVE_HOST');
+ $sftpUser = getenv('HIDRIVE_USER');
+ $sftpPass = getenv('HIDRIVE_PASS');
$remotePath = '/users/erp2019/backups_erp/' . $remoteFilename;
$sftp = new SFTP($sftpHost);
diff --git a/ci4/app/Database/Migrations/2025-06-09-110500_CreateRestoresTable.php b/ci4/app/Database/Migrations/2025-06-09-110500_CreateRestoresTable.php
index 16ecf321..63ea6d2a 100644
--- a/ci4/app/Database/Migrations/2025-06-09-110500_CreateRestoresTable.php
+++ b/ci4/app/Database/Migrations/2025-06-09-110500_CreateRestoresTable.php
@@ -16,11 +16,11 @@ class CreateRestoresTable extends Migration
]);
$this->forge->addKey('id', true);
$this->forge->addForeignKey('backup_id', 'backups', 'id', 'CASCADE', 'CASCADE');
- $this->forge->createTable('restores');
+ $this->forge->createTable('backup_restores');
}
public function down()
{
- $this->forge->dropTable('restores');
+ $this->forge->dropTable('backup_restores');
}
}
diff --git a/ci4/app/Views/themes/vuexy/form/backups/backupList.php b/ci4/app/Views/themes/vuexy/form/backups/backupList.php
index 6984f811..2b8be11e 100644
--- a/ci4/app/Views/themes/vuexy/form/backups/backupList.php
+++ b/ci4/app/Views/themes/vuexy/form/backups/backupList.php
@@ -1,70 +1,111 @@
= $this->include("themes/_commonPartialsBs/select2bs5") ?>
= $this->include("themes/_commonPartialsBs/datatables") ?>
+= $this->include("themes/_commonPartialsBs/sweetalert") ?>
= $this->extend('themes/vuexy/main/defaultlayout') ?>
-= $this->section('content'); ?>
-
+= $this->section("content") ?>
-
-
Backups disponibles
-
Crear backup
+
+
+
-
-
-
-
- | Archivo |
- Fecha |
- Tamaño |
- Local |
- Remoto |
- Acciones |
-
-
-
-
-
- | = esc($b['filename']) ?> |
- = esc($b['fecha']) ?> |
- = esc($b['tamano']) ?> |
-
- = $b['local'] ? 'Sí' : 'No' ?>
- |
-
- = $b['remoto'] ? 'Sí' : 'No' ?>
- |
-
-
- Restaurar Local
- Eliminar local
-
- Restaurar Remoto
-
- No disponible
-
- |
-
-
-
-
+
+ = view("themes/_commonPartialsBs/_alertBoxes") ?>
-
+
+
Crear backup
+
+
Crear backup desarrollo
+
- = session('message') ?>
-
-
+
+
+
+
+
+
= $this->endSection() ?>
= $this->section('additionalInlineJs') ?>
$(document).ready(function () {
- $('#tablaBackups').DataTable({
- order: [[1, 'desc']],
- language: {
- url: '/assets/vendor/datatables/i18n/es-ES.json' // ajusta si usas idioma español
- }
- });
+$('#tablaBackups').DataTable({
+order: [[1, 'desc']]
+});
});
= $this->endSection() ?>
\ No newline at end of file