Files
whatsapp/modules/usuarios/views/modulos.php
T
2026-04-16 22:23:33 -05:00

191 lines
7.7 KiB
PHP

<?php
/**
* modules/usuarios/views/modulos.php
* Interfaz de administración de módulos del ERP.
* Acceso: /erp.php?m=usuarios&v=modulos
*
* Permite al superadmin / admin:
* - Ver todos los módulos registrados
* - Activar / desactivar módulos con toggle
* - Re-ordenar sort_order
*/
if (!defined('APP_ROOT')) {
define('APP_ROOT', dirname(__DIR__, 3));
}
require_once APP_ROOT . '/config/config.php';
require_once APP_ROOT . '/core/Auth.php';
require_once APP_ROOT . '/core/Rbac.php';
require_once APP_ROOT . '/core/Helpers.php';
require_once APP_ROOT . '/core/ModuleRegistry.php';
require_once APP_ROOT . '/core/Layout.php';
Auth::requireLogin();
Rbac::requireModule('usuarios');
// ── Manejador de acciones AJAX ────────────────────────────────────────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: application/json; charset=utf-8');
$body = inputJson();
$action = $body['action'] ?? '';
switch ($action) {
case 'toggle':
$slug = preg_replace('/[^a-z0-9_]/', '', $body['slug'] ?? '');
$active = (bool)($body['active'] ?? false);
if (!$slug) { jsonError('Slug inválido'); }
$ok = ModuleRegistry::setActive($slug, $active);
$ok ? jsonOk([], $active ? 'Módulo activado' : 'Módulo desactivado') : jsonError('No se pudo actualizar', 500);
break;
case 'reorder':
$slug = preg_replace('/[^a-z0-9_]/', '', $body['slug'] ?? '');
$order = (int)($body['order'] ?? 99);
if (!$slug) { jsonError('Slug inválido'); }
$ok = ModuleRegistry::setSortOrder($slug, $order);
$ok ? jsonOk([], 'Orden actualizado') : jsonError('No se pudo actualizar', 500);
break;
default:
jsonError('Acción desconocida');
}
exit;
}
// ── Vista HTML ────────────────────────────────────────────────────────────────
$modules = ModuleRegistry::getAll();
// Etiquetas de oleada
$oleadaLabel = [0 => 'Producción', 1 => 'Oleada 1', 2 => 'Oleada 2'];
$oleadaBadge = [0 => 'bg-success', 1 => 'bg-primary', 2 => 'bg-secondary'];
Layout::open('Gestión de Módulos', 'fas fa-th-large');
?>
<div class="d-flex justify-content-between align-items-center mb-3">
<p class="text-muted mb-0">
Activa o desactiva módulos del ERP. Los módulos desactivados no aparecen en el sidebar
ni son accesibles para ningún usuario.
</p>
<button class="btn btn-outline-secondary btn-sm" onclick="location.reload()">
<i class="fas fa-sync-alt me-1"></i>Recargar
</button>
</div>
<!-- Tabla de módulos -->
<div class="card shadow-sm">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>Módulo</th>
<th>Slug</th>
<th>Categoría</th>
<th>Oleada</th>
<th class="text-center">Orden</th>
<th class="text-center">Activo</th>
</tr>
</thead>
<tbody>
<?php
$sorted = $modules;
uasort($sorted, fn($a, $b) => [$a['oleada'], $a['sort_order']] <=> [$b['oleada'], $b['sort_order']]);
foreach ($sorted as $slug => $mod):
$active = $mod['is_active'];
$oleadaIdx = (int)($mod['oleada'] ?? 0);
$badgeClass = $oleadaBadge[$oleadaIdx] ?? 'bg-secondary';
$badgeText = $oleadaLabel[$oleadaIdx] ?? "Oleada $oleadaIdx";
?>
<tr data-slug="<?= esc($slug) ?>">
<td>
<i class="<?= esc($mod['icon']) ?> me-2 text-muted"></i>
<strong><?= esc($mod['name']) ?></strong>
<?php if ($mod['description']): ?>
<br><small class="text-muted"><?= esc($mod['description']) ?></small>
<?php endif; ?>
</td>
<td><code class="small"><?= esc($slug) ?></code></td>
<td><span class="badge bg-light text-dark border"><?= esc($mod['category']) ?></span></td>
<td><span class="badge <?= $badgeClass ?>"><?= esc($badgeText) ?></span></td>
<td class="text-center">
<input type="number" class="form-control form-control-sm text-center order-input"
value="<?= (int)$mod['sort_order'] ?>" min="1" max="999"
data-slug="<?= esc($slug) ?>" style="width:70px;margin:auto;">
</td>
<td class="text-center">
<div class="form-check form-switch d-inline-block">
<input class="form-check-input toggle-module" type="checkbox"
role="switch"
id="mod-<?= esc($slug) ?>"
data-slug="<?= esc($slug) ?>"
<?= $active ? 'checked' : '' ?>
style="cursor:pointer;width:2.5rem;height:1.25rem;">
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<p class="text-muted small mt-3">
<i class="fas fa-info-circle me-1"></i>
Los cambios se aplican de inmediato. Si un módulo desactivado tiene permisos asignados en roles,
esos permisos se conservan en BD pero el módulo no será accesible hasta reactivarlo.
</p>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Toggle activo/inactivo
document.querySelectorAll('.toggle-module').forEach(toggle => {
toggle.addEventListener('change', async function () {
const slug = this.dataset.slug;
const active = this.checked;
this.disabled = true;
try {
await erp.fetch('', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({action: 'toggle', slug, active})
});
erp.toast(active ? `Módulo "${slug}" activado` : `Módulo "${slug}" desactivado`,
active ? 'success' : 'warning');
} catch (e) {
this.checked = !active; // revertir
erp.toast('Error al actualizar: ' + e.message, 'danger');
} finally {
this.disabled = false;
}
});
});
// Cambiar orden (al perder el foco)
document.querySelectorAll('.order-input').forEach(input => {
input.addEventListener('change', async function () {
const slug = this.dataset.slug;
const order = parseInt(this.value, 10);
if (isNaN(order) || order < 1) return;
try {
await erp.fetch('', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({action: 'reorder', slug, order})
});
erp.toast(`Orden de "${slug}" actualizado`, 'info');
} catch (e) {
erp.toast('Error al actualizar: ' + e.message, 'danger');
}
});
});
});
</script>
<?php
Layout::close();