This commit is contained in:
Lizandro Guarnizo
2026-04-16 22:23:33 -05:00
parent 50b7f7f8ca
commit ef822dd3c7
38 changed files with 2448 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
<?php return ['slug'=>'enfermero_portal','name'=>'Portal Enfermero','icon'=>'fas fa-stethoscope','category'=>'sistema','route'=>'/enfermero_portal.php','is_active'=>true,'sort_order'=>90,'oleada'=>0,'description'=>'Portal exclusivo para el personal de enfermería','links'=>[['name'=>'Portal Enfermero','icon'=>'fas fa-stethoscope','route'=>'/enfermero_portal.php']]];
+10
View File
@@ -0,0 +1,10 @@
<?php
/**
* modules/enfermero_portal/views/index.php
* Bridge al archivo legacy mientras se migra al nuevo sistema ERP.
* Acceso via: /erp.php?m=enfermero_portal&v=index o /enfermero_portal/index
*/
if (!defined('APP_ROOT')) {
define('APP_ROOT', dirname(__DIR__, 3));
}
require_once APP_ROOT . '/enfermero_portal.php';
+1
View File
@@ -0,0 +1 @@
<?php return ['slug'=>'lab_configuracion','name'=>'Configuración Lab','icon'=>'fas fa-sliders-h','category'=>'sistema','route'=>'/lab_configuracion.php','is_active'=>true,'sort_order'=>80,'oleada'=>0,'description'=>'Tarifas, laboratorio y configuraciones del sistema','links'=>[['name'=>'Configuración','icon'=>'fas fa-sliders-h','route'=>'/lab_configuracion.php']]];
+10
View File
@@ -0,0 +1,10 @@
<?php
/**
* modules/lab_configuracion/views/index.php
* Bridge al archivo legacy mientras se migra al nuevo sistema ERP.
* Acceso via: /erp.php?m=lab_configuracion&v=index o /lab_configuracion/index
*/
if (!defined('APP_ROOT')) {
define('APP_ROOT', dirname(__DIR__, 3));
}
require_once APP_ROOT . '/lab_configuracion.php';
+1
View File
@@ -0,0 +1 @@
<?php return ['slug'=>'lab_dashboard','name'=>'Dashboard Lab','icon'=>'fas fa-tachometer-alt','category'=>'lab','route'=>'/lab_dashboard.php','is_active'=>true,'sort_order'=>10,'oleada'=>0,'description'=>'Panel principal del módulo de laboratorio','links'=>[['name'=>'Dashboard','icon'=>'fas fa-tachometer-alt','route'=>'/lab_dashboard.php']]];
+10
View File
@@ -0,0 +1,10 @@
<?php
/**
* modules/lab_dashboard/views/index.php
* Bridge al archivo legacy mientras se migra al nuevo sistema ERP.
* Acceso via: /erp.php?m=lab_dashboard&v=index o /lab_dashboard/index
*/
if (!defined('APP_ROOT')) {
define('APP_ROOT', dirname(__DIR__, 3));
}
require_once APP_ROOT . '/lab_dashboard.php';
+1
View File
@@ -0,0 +1 @@
<?php return ['slug'=>'lab_domicilios','name'=>'Domicilios','icon'=>'fas fa-house-medical','category'=>'lab','route'=>'/lab_domicilios.php','is_active'=>true,'sort_order'=>20,'oleada'=>0,'description'=>'Agenda y gestión de servicios a domicilio','links'=>[['name'=>'Domicilios','icon'=>'fas fa-house-medical','route'=>'/lab_domicilios.php']]];
+10
View File
@@ -0,0 +1,10 @@
<?php
/**
* modules/lab_domicilios/views/index.php
* Bridge al archivo legacy mientras se migra al nuevo sistema ERP.
* Acceso via: /erp.php?m=lab_domicilios&v=index o /lab_domicilios/index
*/
if (!defined('APP_ROOT')) {
define('APP_ROOT', dirname(__DIR__, 3));
}
require_once APP_ROOT . '/lab_domicilios.php';
+1
View File
@@ -0,0 +1 @@
<?php return ['slug'=>'lab_enfermeras','name'=>'Enfermeros','icon'=>'fas fa-user-nurse','category'=>'lab','route'=>'/lab_enfermeras.php','is_active'=>true,'sort_order'=>22,'oleada'=>0,'description'=>'Gestión de personal clínico y enfermeros','links'=>[['name'=>'Enfermeros','icon'=>'fas fa-user-nurse','route'=>'/lab_enfermeras.php']]];
+10
View File
@@ -0,0 +1,10 @@
<?php
/**
* modules/lab_enfermeras/views/index.php
* Bridge al archivo legacy mientras se migra al nuevo sistema ERP.
* Acceso via: /erp.php?m=lab_enfermeras&v=index o /lab_enfermeras/index
*/
if (!defined('APP_ROOT')) {
define('APP_ROOT', dirname(__DIR__, 3));
}
require_once APP_ROOT . '/lab_enfermeras.php';
+1
View File
@@ -0,0 +1 @@
<?php return ['slug'=>'lab_formularios','name'=>'Formularios','icon'=>'fas fa-wpforms','category'=>'lab','route'=>'/lab_formularios.php','is_active'=>true,'sort_order'=>24,'oleada'=>0,'description'=>'Builder de formularios drag-drop con firma digital','links'=>[['name'=>'Formularios','icon'=>'fas fa-wpforms','route'=>'/lab_formularios.php']]];
+10
View File
@@ -0,0 +1,10 @@
<?php
/**
* modules/lab_formularios/views/index.php
* Bridge al archivo legacy mientras se migra al nuevo sistema ERP.
* Acceso via: /erp.php?m=lab_formularios&v=index o /lab_formularios/index
*/
if (!defined('APP_ROOT')) {
define('APP_ROOT', dirname(__DIR__, 3));
}
require_once APP_ROOT . '/lab_formularios.php';
+1
View File
@@ -0,0 +1 @@
<?php return ['slug'=>'lab_ordenes','name'=>'Órdenes Médicas','icon'=>'fas fa-file-medical','category'=>'lab','route'=>'/lab_ordenes.php','is_active'=>true,'sort_order'=>23,'oleada'=>0,'description'=>'Gestión de órdenes médicas con PDF y autorización','links'=>[['name'=>'Órdenes','icon'=>'fas fa-file-medical','route'=>'/lab_ordenes.php']]];
+10
View File
@@ -0,0 +1,10 @@
<?php
/**
* modules/lab_ordenes/views/index.php
* Bridge al archivo legacy mientras se migra al nuevo sistema ERP.
* Acceso via: /erp.php?m=lab_ordenes&v=index o /lab_ordenes/index
*/
if (!defined('APP_ROOT')) {
define('APP_ROOT', dirname(__DIR__, 3));
}
require_once APP_ROOT . '/lab_ordenes.php';
+1
View File
@@ -0,0 +1 @@
<?php return ['slug'=>'lab_pacientes','name'=>'Pacientes','icon'=>'fas fa-users','category'=>'lab','route'=>'/lab_pacientes.php','is_active'=>true,'sort_order'=>21,'oleada'=>0,'description'=>'Fichas clínicas e historial de pacientes','links'=>[['name'=>'Pacientes','icon'=>'fas fa-users','route'=>'/lab_pacientes.php']]];
+10
View File
@@ -0,0 +1,10 @@
<?php
/**
* modules/lab_pacientes/views/index.php
* Bridge al archivo legacy mientras se migra al nuevo sistema ERP.
* Acceso via: /erp.php?m=lab_pacientes&v=index o /lab_pacientes/index
*/
if (!defined('APP_ROOT')) {
define('APP_ROOT', dirname(__DIR__, 3));
}
require_once APP_ROOT . '/lab_pacientes.php';
+1
View File
@@ -0,0 +1 @@
<?php return ['slug'=>'lab_reportes','name'=>'Reportes','icon'=>'fas fa-chart-bar','category'=>'reportes','route'=>'/lab_reportes.php','is_active'=>true,'sort_order'=>50,'oleada'=>0,'description'=>'Reportes de ingresos, rendimiento y pagos','links'=>[['name'=>'Reportes','icon'=>'fas fa-chart-bar','route'=>'/lab_reportes.php'],['name'=>'Actividad','icon'=>'fas fa-history','route'=>'/lab_actividad.php']]];
+10
View File
@@ -0,0 +1,10 @@
<?php
/**
* modules/lab_reportes/views/index.php
* Bridge al archivo legacy mientras se migra al nuevo sistema ERP.
* Acceso via: /erp.php?m=lab_reportes&v=index o /lab_reportes/index
*/
if (!defined('APP_ROOT')) {
define('APP_ROOT', dirname(__DIR__, 3));
}
require_once APP_ROOT . '/lab_reportes.php';
+1
View File
@@ -0,0 +1 @@
<?php return ['slug'=>'usuarios','name'=>'Usuarios & Roles','icon'=>'fas fa-users-cog','category'=>'sistema','route'=>'/lab_usuarios.php','is_active'=>true,'sort_order'=>81,'oleada'=>0,'description'=>'Gestión de usuarios del sistema y asignación de roles','links'=>[['name'=>'Usuarios & Roles','icon'=>'fas fa-users-cog','route'=>'/lab_usuarios.php'],['name'=>'Módulos','icon'=>'fas fa-th-large','route'=>'/erp.php?m=usuarios&v=modulos']]];
+10
View File
@@ -0,0 +1,10 @@
<?php
/**
* modules/usuarios/views/index.php
* Bridge al archivo legacy mientras se migra al nuevo sistema ERP.
* Acceso via: /erp.php?m=usuarios&v=index o /usuarios/index
*/
if (!defined('APP_ROOT')) {
define('APP_ROOT', dirname(__DIR__, 3));
}
require_once APP_ROOT . '/lab_usuarios.php';
+190
View File
@@ -0,0 +1,190 @@
<?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();
+22
View File
@@ -0,0 +1,22 @@
<?php
/**
* modules/whatsapp/module.php
* Descriptor del módulo WhatsApp Bot.
*/
return [
'slug' => 'whatsapp',
'name' => 'WhatsApp Bot',
'icon' => 'fab fa-whatsapp',
'category' => 'bot',
'route' => '/index.php',
'is_active' => true,
'sort_order' => 1,
'oleada' => 0,
'description' => 'Bot, conversaciones, plantillas y menús de WhatsApp',
'links' => [
['name' => 'Conversaciones', 'icon' => 'fas fa-comments', 'route' => '/conversations.php'],
['name' => 'Bot & Menús', 'icon' => 'fas fa-robot', 'route' => '/index.php'],
['name' => 'Plantillas', 'icon' => 'fas fa-file-alt', 'route' => '/index.php#templates'],
['name' => 'Programados', 'icon' => 'fas fa-clock', 'route' => '/scheduled_messages.php'],
],
];
+10
View File
@@ -0,0 +1,10 @@
<?php
/**
* modules/whatsapp/views/index.php
* Bridge al archivo legacy mientras se migra al nuevo sistema ERP.
* Acceso via: /erp.php?m=whatsapp&v=index o /whatsapp/index
*/
if (!defined('APP_ROOT')) {
define('APP_ROOT', dirname(__DIR__, 3));
}
require_once APP_ROOT . '/index.php';