Files
Axia4/public_html/sysadmin/users.php
copilot-swe-agent[bot] 0c362fd40b feat: SQLite DB with migrations replaces all JSON file storage
- Add db.php with PDO singleton, migration runner, and all helper functions
- Add migrations/001_initial_schema.sql (full schema)
- Add migrations/002_import_json.php (one-time JSON → DB importer)
- Add _incl/switch_tenant.php POST endpoint for tenant/centro switching
- Update tools.auth.php: DB-backed login, cookie auth, session reload, init_active_centro()
- Update all sysadmin pages (users, centros, aularios, invitations, reset_password) to use DB
- Update aulatek/index.php, aulario.php, supercafe.php, supercafe_edit.php to use DB
- Update aulatek/comedor.php and api/comedor.php to use DB
- Update aulatek/paneldiario.php: aulario config + comedor data from DB
- Update aulatek/proyectos.php: aulario config + sharing metadata from DB
- Update club/cal.php, index.php, edit_data.php, upload/upload.php to use DB
- Update account/index.php: rich profile, tenant list, aula list, session info, permissions
- Update pre-body.php account dropdown: shows active org + inline tenant switcher
- Update DATA_STRUCTURE.md to document DB approach and migration system

Co-authored-by: naielv <109038805+naielv@users.noreply.github.com>
2026-03-06 22:00:48 +00:00

323 lines
15 KiB
PHP

<?php
require_once "_incl/auth_redir.php";
require_once "../_incl/tools.security.php";
require_once "../_incl/db.php";
function safe_username($value)
{
$value = strtolower(basename((string) $value));
$value = preg_replace('/[^a-zA-Z0-9._@-]/', '', $value);
if (strpos($value, '..') !== false) {
return '';
}
return $value;
}
switch ($_GET['form'] ?? '') {
case 'save_edit':
$username = safe_username($_POST['username'] ?? '');
if (empty($username)) {
die("Nombre de usuario no proporcionado.");
}
$permissions = $_POST['permissions'] ?? [];
if (!is_array($permissions)) {
$permissions = [];
}
$aulas = $_POST['aulas'] ?? [];
if (!is_array($aulas)) {
$aulas = [];
}
$aulas = array_values(array_filter(array_map('safe_aulario_id', $aulas)));
db_upsert_user([
'username' => $username,
'display_name' => $_POST['display_name'] ?? '',
'email' => $_POST['email'] ?? '',
'permissions' => $permissions,
'entreaulas' => [
'centro' => safe_centro_id($_POST['centro'] ?? ''),
'role' => $_POST['role'] ?? '',
'aulas' => $aulas,
],
]);
header("Location: ?action=edit&user=" . urlencode($username) . "&_result=" . urlencode("Cambios guardados correctamente a las " . date("H:i:s") . " (hora servidor)."));
exit;
break;
}
switch ($_GET['action'] ?? '') {
case 'add':
require_once "_incl/pre-body.php";
$all_centros = db_get_centro_ids();
?>
<form method="post" action="?form=save_edit">
<div class="card pad">
<div>
<h1 class="card-title">Agregar Nuevo Usuario</h1>
<div class="mb-3">
<label for="username" class="form-label">Nombre de usuario:</label>
<input type="text" id="username" name="username" class="form-control" required>
</div>
<div class="mb-3">
<label for="display_name" class="form-label">Nombre para mostrar:</label>
<input type="text" id="display_name" name="display_name" class="form-control" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Correo electrónico:</label>
<input type="email" id="email" name="email" class="form-control" required>
</div>
<b>Permisos:</b>
<div class="accordion mt-3" id="permissionsAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSysadmin">Administración del sistema</button>
</h2>
<div id="collapseSysadmin" class="accordion-collapse collapse show" data-bs-parent="#permissionsAccordion">
<div class="accordion-body">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="sysadmin:access" id="sysadmin-access">
<label class="form-check-label" for="sysadmin-access">Acceso</label>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseEntreaulas">EntreAulas</button>
</h2>
<div id="collapseEntreaulas" class="accordion-collapse collapse" data-bs-parent="#permissionsAccordion">
<div class="accordion-body">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:access" id="entreaulas-access">
<label class="form-check-label" for="entreaulas-access">Acceso</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:docente" id="entreaulas-docente">
<label class="form-check-label" for="entreaulas-docente">Docente</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:proyectos:delete" id="entreaulas-proyectos-delete">
<label class="form-check-label" for="entreaulas-proyectos-delete">Eliminar Proyectos</label>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSupercafe">SuperCafe</button>
</h2>
<div id="collapseSupercafe" class="accordion-collapse collapse" data-bs-parent="#permissionsAccordion">
<div class="accordion-body">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="supercafe:access" id="supercafe-access">
<label class="form-check-label" for="supercafe-access">Acceso</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="supercafe:edit" id="supercafe-edit">
<label class="form-check-label" for="supercafe-edit">Editar comandas</label>
</div>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary mt-3">Crear Usuario</button>
</div>
</div>
<div class="card pad">
<div>
<h2>EntreAulas: Configuración</h2>
<div class="mb-3">
<label for="centro" class="form-label">Centro asociado:</label>
<select id="centro" name="centro" class="form-select">
<option value="">-- Selecciona un centro --</option>
<?php foreach ($all_centros as $cid): ?>
<option value="<?= htmlspecialchars($cid) ?>"><?= htmlspecialchars($cid) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label for="role" class="form-label">Rol en EntreAulas:</label>
<select id="role" name="role" class="form-select">
<option value="">-- Selecciona un rol --</option>
<option value="teacher">Profesor</option>
<option value="student">Estudiante</option>
</select>
</div>
<p class="text-muted"><small>Las aulas podrán asignarse tras guardar el usuario.</small></p>
</div>
</div>
</form>
<?php
require_once "_incl/post-body.php";
break;
case 'edit':
require_once "_incl/pre-body.php";
$username = safe_username($_GET['user'] ?? '');
if (empty($username)) {
die("Nombre de usuario inválido.");
}
$row = db_get_user($username);
if (!$row) {
die("Usuario no encontrado.");
}
$userdata = db_build_auth_data($row);
$all_centros = db_get_centro_ids();
$user_centro = safe_centro_id($userdata['entreaulas']['centro'] ?? '');
$aularios = $user_centro !== '' ? db_get_aularios($user_centro) : [];
?>
<form method="post" action="?form=save_edit">
<div class="card pad">
<div>
<h1>Editar Usuario: <?= htmlspecialchars($username) ?></h1>
<div class="mb-3">
<label for="display_name" class="form-label">Nombre para mostrar:</label>
<input type="text" id="display_name" name="display_name" value="<?= htmlspecialchars($userdata['display_name'] ?? '') ?>" class="form-control" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Correo electrónico:</label>
<input type="email" id="email" name="email" value="<?= htmlspecialchars($userdata['email'] ?? '') ?>" class="form-control" required>
</div>
<b>Permisos:</b>
<div class="accordion mt-3" id="permissionsAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSysadmin">Administración del sistema</button>
</h2>
<div id="collapseSysadmin" class="accordion-collapse collapse show" data-bs-parent="#permissionsAccordion">
<div class="accordion-body">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="sysadmin:access" id="sysadmin-access" <?= in_array('sysadmin:access', $userdata['permissions'] ?? []) ? 'checked' : '' ?>>
<label class="form-check-label" for="sysadmin-access">Acceso</label>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseEntreaulas">EntreAulas</button>
</h2>
<div id="collapseEntreaulas" class="accordion-collapse collapse" data-bs-parent="#permissionsAccordion">
<div class="accordion-body">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:access" id="entreaulas-access" <?= in_array('entreaulas:access', $userdata['permissions'] ?? []) ? 'checked' : '' ?>>
<label class="form-check-label" for="entreaulas-access">Acceso</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:docente" id="entreaulas-docente" <?= in_array('entreaulas:docente', $userdata['permissions'] ?? []) ? 'checked' : '' ?>>
<label class="form-check-label" for="entreaulas-docente">Docente</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:proyectos:delete" id="entreaulas-proyectos-delete" <?= in_array('entreaulas:proyectos:delete', $userdata['permissions'] ?? []) ? 'checked' : '' ?>>
<label class="form-check-label" for="entreaulas-proyectos-delete">Eliminar Proyectos</label>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSupercafe">SuperCafe</button>
</h2>
<div id="collapseSupercafe" class="accordion-collapse collapse" data-bs-parent="#permissionsAccordion">
<div class="accordion-body">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="supercafe:access" id="supercafe-access" <?= in_array('supercafe:access', $userdata['permissions'] ?? []) ? 'checked' : '' ?>>
<label class="form-check-label" for="supercafe-access">Acceso</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="supercafe:edit" id="supercafe-edit" <?= in_array('supercafe:edit', $userdata['permissions'] ?? []) ? 'checked' : '' ?>>
<label class="form-check-label" for="supercafe-edit">Editar comandas</label>
</div>
</div>
</div>
</div>
</div>
<input type="hidden" name="username" value="<?= htmlspecialchars($username) ?>">
<button type="submit" class="btn btn-primary mt-3">Guardar Cambios</button>
</div>
</div>
<div class="card pad">
<div>
<h2>EntreAulas: Configuración</h2>
<div class="mb-3">
<label for="centro" class="form-label">Centro asociado:</label>
<select id="centro" name="centro" class="form-select" required>
<option value="" <?= empty($user_centro) ? 'selected' : '' ?>>-- Selecciona un centro --</option>
<?php foreach ($all_centros as $cid): ?>
<option value="<?= htmlspecialchars($cid) ?>" <?= $user_centro === $cid ? 'selected' : '' ?>><?= htmlspecialchars($cid) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label for="role" class="form-label">Rol en EntreAulas:</label>
<select id="role" name="role" class="form-select" required>
<option value="" <?= empty($userdata['entreaulas']['role'] ?? '') ? 'selected' : '' ?>>-- Selecciona un rol --</option>
<option value="teacher" <?= ($userdata['entreaulas']['role'] ?? '') === 'teacher' ? 'selected' : '' ?>>Profesor</option>
<option value="student" <?= ($userdata['entreaulas']['role'] ?? '') === 'student' ? 'selected' : '' ?>>Estudiante</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Aulas asignadas: <small>(Guarda primero para actualizar la lista)</small></label><br>
<?php foreach ($aularios as $aula_id => $aula_data): ?>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="aulas[]"
value="<?= htmlspecialchars($aula_id) ?>"
id="aula-<?= htmlspecialchars($aula_id) ?>"
<?= in_array($aula_id, $userdata['entreaulas']['aulas'] ?? []) ? 'checked' : '' ?>>
<label class="form-check-label" for="aula-<?= htmlspecialchars($aula_id) ?>">
<?= htmlspecialchars($aula_data['name'] ?? $aula_id) ?>
</label>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="card pad">
<div>
<h2>Cambiar contraseña</h2>
<a href="/sysadmin/reset_password.php?user=<?= urlencode($username) ?>" class="btn btn-secondary">Restablecer Contraseña</a>
</div>
</div>
</form>
<?php
require_once "_incl/post-body.php";
break;
case 'index':
default:
require_once "_incl/pre-body.php";
$all_users = db_get_all_users();
?>
<div class="card pad">
<div>
<h1>Gestión de Usuarios</h1>
<p>Desde esta sección puedes gestionar los usuarios del sistema.</p>
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>Usuario</th>
<th>Nombre</th>
<th>Correo</th>
<th><a href="?action=add" class="btn btn-success">+ Nuevo</a></th>
</tr>
</thead>
<tbody>
<?php foreach ($all_users as $u): ?>
<tr>
<td><?= htmlspecialchars($u['username']) ?></td>
<td><?= htmlspecialchars($u['display_name'] ?: 'N/A') ?></td>
<td><?= htmlspecialchars($u['email'] ?: 'N/A') ?></td>
<td>
<a href="?action=edit&user=<?= urlencode($u['username']) ?>" class="btn btn-primary">Editar</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php
require_once "_incl/post-body.php";
break;
}