diff --git a/DATA_STRUCTURE.md b/DATA_STRUCTURE.md
index ce36205..f972ead 100644
--- a/DATA_STRUCTURE.md
+++ b/DATA_STRUCTURE.md
@@ -1,4 +1,65 @@
-# Example Data Structure for Axia4
+# Data Architecture for Axia4
+
+Axia4 uses a **SQLite database** (`/DATA/axia4.sqlite`) for all structured data, with the
+filesystem reserved for binary assets (photos, uploaded files, project documents).
+
+## Database (`/DATA/axia4.sqlite`)
+
+The schema is defined in `public_html/_incl/migrations/001_initial_schema.sql` and
+applied automatically on first boot via `db.php`.
+
+| Table | Replaces |
+|----------------------|---------------------------------------------------|
+| `config` | `/DATA/AuthConfig.json` |
+| `users` | `/DATA/Usuarios/*.json` |
+| `invitations` | `/DATA/Invitaciones_de_usuarios.json` |
+| `centros` | Directory existence at `.../Centros/{id}/` |
+| `user_centros` | `entreaulas.centro` + `entreaulas.aulas` in users |
+| `aularios` | `.../Aularios/{id}.json` |
+| `supercafe_menu` | `.../SuperCafe/Menu.json` |
+| `supercafe_orders` | `.../SuperCafe/Comandas/*.json` |
+| `comedor_menu_types` | `.../Comedor-MenuTypes.json` |
+| `comedor_entries` | `.../Comedor/{ym}/{day}/_datos.json` |
+| `club_events` | `/DATA/club/IMG/{date}/data.json` |
+| `club_config` | `/DATA/club/config.json` |
+
+## Migrations
+
+Migrations live in `public_html/_incl/migrations/`:
+
+- `001_initial_schema.sql` — DDL for all tables.
+- `002_import_json.php` — One-time importer: reads existing JSON files and
+ inserts them into the database. Run automatically on first boot if JSON files
+ exist and the DB is empty.
+
+## Filesystem (binary / large assets)
+
+```
+DATA/
+└── entreaulas/
+ └── Centros/
+ └── {centro_id}/
+ ├── Aularios/
+ │ └── {aulario_id}/
+ │ ├── Alumnos/ # Student photo directories
+ │ │ └── {alumno}/photo.jpg
+ │ ├── Comedor/{ym}/{day}/ # Comedor pictogram images
+ │ └── Proyectos/ # Project binary files
+ └── Panel/
+ └── Actividades/{name}/photo.jpg # Activity photos
+└── club/
+ └── IMG/{date}/ # Club event photos (still on filesystem)
+```
+
+## Multi-Tenant Support
+
+A user can belong to **multiple centros** (organizations). The active centro
+is stored in `$_SESSION['active_centro']` and can be switched at any time via
+`POST /_incl/switch_tenant.php`.
+
+The account page (`/account/`) shows all assigned organizations and lets the
+user switch between them.
+
This directory contains example data files that demonstrate the structure needed for the Axia4 application.
diff --git a/Dockerfile b/Dockerfile
index a7d1076..659d075 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,7 +8,7 @@ FROM dunglas/frankenphp
# && rm -rf /var/lib/apt/lists/*
# Configure PHP extensions
-RUN install-php-extensions gd opcache
+RUN install-php-extensions gd opcache pdo pdo_sqlite
# Set working directory
WORKDIR /var/www/html
diff --git a/Dockerfile.dev b/Dockerfile.dev
index 2b975b6..10a1f71 100644
--- a/Dockerfile.dev
+++ b/Dockerfile.dev
@@ -8,7 +8,7 @@ FROM dunglas/frankenphp
# && rm -rf /var/lib/apt/lists/*
# Configure PHP extensions
-RUN install-php-extensions gd opcache
+RUN install-php-extensions gd opcache pdo pdo_sqlite
# Set working directory
WORKDIR /var/www/html
diff --git a/README.md b/README.md
index 3dccc19..bee20d3 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Axia4
-Axia4 is a unified platform for EuskadiTech and Sketaria, providing various services including EntreAulas (connected classroom management system).
+Axia4 is a unified platform for EuskadiTech and Sketaria, providing various services including AulaTek (connected classroom management system).
## Quick Start with Docker
@@ -13,7 +13,7 @@ cd Axia4
# 2. Create the data directory structure
mkdir -p DATA/entreaulas/Usuarios
-mkdir -p DATA/entreaulas/Centros
+mkdir -p DATA/entreaulas/Organizaciones
# 3. Start the application
docker compose up -d
@@ -29,7 +29,7 @@ docker compose up -d
## Features
-- **EntreAulas**: Management system for connected classrooms
+- **AulaTek**: Management system for connected classrooms
- **Aularios**: Centralized access to classroom resources
- Integration with multiple external services
diff --git a/public_html/_incl/db.php b/public_html/_incl/db.php
new file mode 100644
index 0000000..be53e3a
--- /dev/null
+++ b/public_html/_incl/db.php
@@ -0,0 +1,765 @@
+setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
+ $pdo->exec('PRAGMA journal_mode = WAL');
+ $pdo->exec('PRAGMA foreign_keys = ON');
+ $pdo->exec('PRAGMA synchronous = NORMAL');
+
+ db_migrate($pdo);
+
+ return $pdo;
+}
+
+// ── Migration runner ──────────────────────────────────────────────────────────
+
+function db_migrate(PDO $pdo): void
+{
+ $pdo->exec(
+ 'CREATE TABLE IF NOT EXISTS schema_migrations (
+ version INTEGER PRIMARY KEY,
+ applied_at TEXT NOT NULL DEFAULT (datetime(\'now\'))
+ )'
+ );
+
+ $applied = $pdo->query('SELECT version FROM schema_migrations ORDER BY version')
+ ->fetchAll(PDO::FETCH_COLUMN);
+
+ $files = glob(MIGRATIONS_DIR . '/*.{sql,php}', GLOB_BRACE) ?: [];
+ sort($files);
+
+ foreach ($files as $file) {
+ if (!preg_match('/^(\d+)/', basename($file), $m)) {
+ continue;
+ }
+ $version = (int) $m[1];
+ if (in_array($version, $applied, true)) {
+ continue;
+ }
+
+ if (str_ends_with($file, '.sql')) {
+ $pdo->exec((string) file_get_contents($file));
+ } elseif (str_ends_with($file, '.php')) {
+ // PHP migration receives the connection as $db
+ $db = $pdo;
+ require $file;
+ }
+
+ $pdo->prepare('INSERT INTO schema_migrations (version) VALUES (?)')->execute([$version]);
+ }
+}
+
+// ── Config helpers ────────────────────────────────────────────────────────────
+
+function db_get_config(string $key, $default = null)
+{
+ $stmt = db()->prepare('SELECT value FROM config WHERE key = ?');
+ $stmt->execute([$key]);
+ $row = $stmt->fetch();
+ if ($row === false) {
+ return $default;
+ }
+ $decoded = json_decode($row['value'], true);
+ return $decoded !== null ? $decoded : $row['value'];
+}
+
+function db_set_config(string $key, $value): void
+{
+ db()->prepare('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)')
+ ->execute([$key, is_string($value) ? $value : json_encode($value)]);
+}
+
+function db_get_all_config(): array
+{
+ $rows = db()->query('SELECT key, value FROM config')->fetchAll();
+ $result = [];
+ foreach ($rows as $row) {
+ $decoded = json_decode($row['value'], true);
+ $result[$row['key']] = ($decoded !== null) ? $decoded : $row['value'];
+ }
+ return $result;
+}
+
+// ── User helpers ──────────────────────────────────────────────────────────────
+
+/** Find a user by username (always lower-cased). Returns DB row or null. */
+function db_get_user(string $username): ?array
+{
+ $stmt = db()->prepare('SELECT * FROM users WHERE username = ?');
+ $stmt->execute([strtolower($username)]);
+ $row = $stmt->fetch();
+ return $row !== false ? $row : null;
+}
+
+/** Return all user rows ordered by username. */
+function db_get_all_users(): array
+{
+ return db()->query('SELECT * FROM users ORDER BY username')->fetchAll();
+}
+
+/**
+ * Build the auth_data session array from a DB user row.
+ * Preserves the same format existing code expects:
+ * auth_data.permissions, auth_data.active_organizations auth_data.organizations.
+ */
+function db_build_auth_data(array $row): array
+{
+ $permissions = json_decode($row['permissions'] ?? '[]', true) ?: [];
+ $meta = json_decode($row['meta'] ?? '{}', true) ?: [];
+ $ea = [
+ 'organization' => '',
+ 'organizations' => [],
+ 'role' => '',
+ 'aulas' => [],
+ 'organizations_data' => [],
+ ];
+
+ // Fetch all organization assignments for this user
+ $stmt = db()->prepare(
+ 'SELECT org_id, role, ea_aulas
+ FROM user_orgs
+ WHERE user_id = ?
+ ORDER BY org_id'
+ );
+ $stmt->execute([$row['id']]);
+ $org_rows = $stmt->fetchAll();
+ $orgs = [];
+ if (!empty($org_rows)) {
+ $first = $org_rows[0];
+ foreach ($org_rows as $r) {
+ $orgs[] = $r['org_id'];
+ }
+ $ea['organization'] = $first['org_id'];
+ $ea['role'] = $first['role'];
+ $ea['aulas'] = json_decode($first['ea_aulas'] ?? '[]', true) ?: [];
+ $ea['organizations'] = $orgs;
+ $ea['organizations_data'] = $org_rows;
+ }
+
+ $active_org = $ea['organization'] ?? '';
+ $aulatek = [
+ 'organizacion' => $active_org,
+ 'organizaciones' => $orgs,
+ 'organization' => $active_org,
+ 'organizations' => $orgs,
+ 'centro' => $active_org,
+ 'centros' => $orgs,
+ 'role' => $ea['role'] ?? '',
+ 'aulas' => $ea['aulas'] ?? [],
+ ];
+
+ return array_merge($meta, [
+ 'display_name' => $row['display_name'],
+ 'email' => $row['email'],
+ 'password_hash' => $row['password_hash'],
+ 'permissions' => $permissions,
+ 'orgs' => $orgs,
+ 'organizations' => $orgs,
+ 'active_organization' => $active_org,
+ 'active_organizations' => $ea,
+ 'aulatek' => $aulatek,
+ 'entreaulas' => $aulatek,
+ 'google_auth' => (bool) $row['google_auth'],
+ ]);
+}
+
+/**
+ * Create or update a user.
+ * $data keys: username, display_name, email, password_hash, permissions[],
+ * google_auth, entreaulas{organizacion,organizaciones[],role,aulas[]}, + any extra meta.
+ * Returns the user ID.
+ */
+function db_upsert_user(array $data): int
+{
+ $pdo = db();
+ $username = strtolower((string) ($data['username'] ?? ''));
+
+ $existing = $pdo->prepare('SELECT id FROM users WHERE username = ?');
+ $existing->execute([$username]);
+ $existing_row = $existing->fetch();
+
+ $permissions = json_encode($data['permissions'] ?? []);
+ $meta_skip = ['username', 'display_name', 'email', 'password_hash',
+ 'permissions', 'entreaulas', 'google_auth',
+ 'orgs', 'organizations', 'organization', 'organizacion',
+ 'role', 'aulas'];
+ $meta = [];
+ foreach ($data as $k => $v) {
+ if (!in_array($k, $meta_skip, true)) {
+ $meta[$k] = $v;
+ }
+ }
+
+ if ($existing_row) {
+ $user_id = (int) $existing_row['id'];
+ $upd = $pdo->prepare(
+ "UPDATE users SET
+ display_name = ?,
+ email = ?,
+ permissions = ?,
+ google_auth = ?,
+ meta = ?,
+ updated_at = datetime('now')
+ WHERE id = ?"
+ );
+ $upd->execute([
+ $data['display_name'] ?? '',
+ $data['email'] ?? '',
+ $permissions,
+ (int) ($data['google_auth'] ?? 0),
+ json_encode($meta),
+ $user_id,
+ ]);
+ if (!empty($data['password_hash'])) {
+ $pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?')
+ ->execute([$data['password_hash'], $user_id]);
+ }
+ } else {
+ $pdo->prepare(
+ 'INSERT INTO users (username, display_name, email, password_hash, permissions, google_auth, meta)
+ VALUES (?, ?, ?, ?, ?, ?, ?)'
+ )->execute([
+ $username,
+ $data['display_name'] ?? '',
+ $data['email'] ?? '',
+ $data['password_hash'] ?? '',
+ $permissions,
+ (int) ($data['google_auth'] ?? 0),
+ json_encode($meta),
+ ]);
+ $user_id = (int) $pdo->lastInsertId();
+ }
+
+ // Update organization assignments if tenant data is provided.
+ $has_org_payload = array_key_exists('entreaulas', $data)
+ || array_key_exists('orgs', $data)
+ || array_key_exists('organizations', $data)
+ || array_key_exists('organization', $data)
+ || array_key_exists('organizacion', $data);
+
+ if ($has_org_payload) {
+ $ea = $data['entreaulas'] ?? [];
+
+ $organizations = [];
+ $candidate_lists = [
+ $data['organizations'] ?? null,
+ $data['orgs'] ?? null,
+ $ea['organizaciones'] ?? null,
+ $ea['organizations'] ?? null,
+ $ea['centros'] ?? null,
+ ];
+ foreach ($candidate_lists as $list) {
+ if (is_array($list) && !empty($list)) {
+ $organizations = $list;
+ break;
+ }
+ }
+ if (empty($organizations)) {
+ foreach ([
+ $data['organization'] ?? null,
+ $data['organizacion'] ?? null,
+ $ea['organizacion'] ?? null,
+ $ea['organization'] ?? null,
+ $ea['centro'] ?? null,
+ ] as $single) {
+ if (!empty($single)) {
+ $organizations = [$single];
+ break;
+ }
+ }
+ }
+
+ $organizations = array_values(array_unique(array_filter(array_map(
+ static function ($value): string {
+ return preg_replace('/[^a-zA-Z0-9._-]/', '', (string) $value);
+ },
+ $organizations
+ ))));
+
+ $role = (string) ($data['role'] ?? $ea['role'] ?? '');
+ $aulas_payload = $data['aulas'] ?? $ea['aulas'] ?? [];
+ if (!is_array($aulas_payload)) {
+ $aulas_payload = [];
+ }
+ $aulas = json_encode($aulas_payload, JSON_UNESCAPED_UNICODE);
+
+ $pdo->prepare('DELETE FROM user_orgs WHERE user_id = ?')->execute([$user_id]);
+
+ $ins_org = $pdo->prepare('INSERT OR IGNORE INTO organizaciones (org_id, org_name) VALUES (?, ?)');
+ $ins_uo = $pdo->prepare(
+ 'INSERT OR REPLACE INTO user_orgs (user_id, org_id, role, ea_aulas) VALUES (?, ?, ?, ?)'
+ );
+ foreach ($organizations as $org_id) {
+ if ($org_id === '') {
+ continue;
+ }
+ $ins_org->execute([$org_id, $org_id]);
+ $ins_uo->execute([$user_id, $org_id, $role, $aulas]);
+ }
+ }
+
+ return $user_id;
+}
+
+/** Delete a user and their organization assignments. */
+function db_delete_user(string $username): void
+{
+ db()->prepare('DELETE FROM users WHERE username = ?')->execute([strtolower($username)]);
+}
+
+// ── Organization helpers ─────────────────────────────────────────────────────
+
+function db_get_organizations(): array
+{
+ return db()->query('SELECT org_id, org_name FROM organizaciones ORDER BY org_id')->fetchAll();
+}
+
+function db_get_organization_ids(): array
+{
+ return db()->query('SELECT org_id FROM organizaciones ORDER BY org_id')->fetchAll(PDO::FETCH_COLUMN);
+}
+
+function db_get_organizaciones(): array
+{
+ return db_get_organizations();
+}
+
+function get_organizations(): array
+{
+ return db_get_organizations();
+}
+
+function db_get_centros(): array
+{
+ $rows = db_get_organizations();
+ return array_map(static function (array $row): array {
+ return [
+ 'centro_id' => $row['org_id'],
+ 'name' => $row['org_name'],
+ ];
+ }, $rows);
+}
+
+function db_get_centro_ids(): array
+{
+ return db_get_organization_ids();
+}
+
+// ── Aulario helpers ───────────────────────────────────────────────────────────
+
+/** Get a single aulario config. Returns merged array (name, icon, + extra fields) or null. */
+function db_get_aulario(string $centro_id, string $aulario_id): ?array
+{
+ $stmt = db()->prepare(
+ 'SELECT name, icon, extra FROM aularios WHERE org_id = ? AND aulario_id = ?'
+ );
+ $stmt->execute([$centro_id, $aulario_id]);
+ $row = $stmt->fetch();
+ if ($row === false) {
+ return null;
+ }
+ $extra = json_decode($row['extra'] ?? '{}', true) ?: [];
+ return array_merge($extra, ['name' => $row['name'], 'icon' => $row['icon']]);
+}
+
+/** Get all aularios for a centro as aulario_id → config array. */
+function db_get_aularios(string $centro_id): array
+{
+ $stmt = db()->prepare(
+ 'SELECT aulario_id, name, icon, extra FROM aularios WHERE org_id = ? ORDER BY aulario_id'
+ );
+ $stmt->execute([$centro_id]);
+ $result = [];
+ foreach ($stmt->fetchAll() as $row) {
+ $extra = json_decode($row['extra'] ?? '{}', true) ?: [];
+ $result[$row['aulario_id']] = array_merge($extra, [
+ 'name' => $row['name'],
+ 'icon' => $row['icon'],
+ ]);
+ }
+ return $result;
+}
+
+// ── SuperCafe helpers ─────────────────────────────────────────────────────────
+
+function db_get_supercafe_menu(string $centro_id): array
+{
+ $stmt = db()->prepare('SELECT data FROM supercafe_menu WHERE org_id = ?');
+ $stmt->execute([$centro_id]);
+ $row = $stmt->fetch();
+ if ($row === false) {
+ return [];
+ }
+ return json_decode($row['data'], true) ?: [];
+}
+
+function db_set_supercafe_menu(string $centro_id, array $menu): void
+{
+ db()->prepare('INSERT OR REPLACE INTO supercafe_menu (org_id, data, updated_at) VALUES (?, ?, datetime(\'now\'))')
+ ->execute([$centro_id, json_encode($menu, JSON_UNESCAPED_UNICODE)]);
+}
+
+/** Return all SC orders for a centro as an array of rows. */
+function db_get_supercafe_orders(string $centro_id): array
+{
+ $stmt = db()->prepare(
+ 'SELECT * FROM supercafe_orders WHERE org_id = ? ORDER BY created_at DESC'
+ );
+ $stmt->execute([$centro_id]);
+ return $stmt->fetchAll();
+}
+
+/** Return a single SC order by ref, or null. */
+function db_get_supercafe_order(string $centro_id, string $order_ref): ?array
+{
+ $stmt = db()->prepare(
+ 'SELECT * FROM supercafe_orders WHERE org_id = ? AND order_ref = ?'
+ );
+ $stmt->execute([$centro_id, $order_ref]);
+ $row = $stmt->fetch();
+ return $row !== false ? $row : null;
+}
+
+/** Create or update an SC order. */
+function db_upsert_supercafe_order(
+ string $centro_id,
+ string $order_ref,
+ string $fecha,
+ string $persona,
+ string $comanda,
+ string $notas,
+ string $estado
+): void {
+ db()->prepare(
+ 'INSERT INTO supercafe_orders (org_id, order_ref, fecha, persona, comanda, notas, estado)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ ON CONFLICT(org_id, order_ref) DO UPDATE SET
+ fecha = excluded.fecha,
+ persona = excluded.persona,
+ comanda = excluded.comanda,
+ notas = excluded.notas,
+ estado = excluded.estado'
+ )->execute([$centro_id, $order_ref, $fecha, $persona, $comanda, $notas, $estado]);
+}
+
+/** Generate the next order_ref for a centro (sc001, sc002, …). */
+function db_next_supercafe_ref(string $centro_id): string
+{
+ $stmt = db()->prepare(
+ "SELECT order_ref FROM supercafe_orders WHERE org_id = ? ORDER BY id DESC LIMIT 1"
+ );
+ $stmt->execute([$centro_id]);
+ $last = $stmt->fetchColumn();
+ $n = 0;
+ if ($last && preg_match('/^sc(\d+)$/', $last, $m)) {
+ $n = (int) $m[1];
+ }
+ return 'sc' . str_pad($n + 1, 3, '0', STR_PAD_LEFT);
+}
+
+/** Count 'Deuda' orders for a persona in a centro. */
+function db_supercafe_count_debts(string $centro_id, string $persona_key): int
+{
+ $stmt = db()->prepare(
+ "SELECT COUNT(*) FROM supercafe_orders WHERE org_id = ? AND persona = ? AND estado = 'Deuda'"
+ );
+ $stmt->execute([$centro_id, $persona_key]);
+ return (int) $stmt->fetchColumn();
+}
+
+// ── Comedor helpers ───────────────────────────────────────────────────────────
+
+function db_get_comedor_menu_types(string $centro_id, string $aulario_id): array
+{
+ $stmt = db()->prepare(
+ 'SELECT data FROM comedor_menu_types WHERE org_id = ? AND aulario_id = ?'
+ );
+ $stmt->execute([$centro_id, $aulario_id]);
+ $row = $stmt->fetch();
+ if ($row === false) {
+ return [];
+ }
+ return json_decode($row['data'], true) ?: [];
+}
+
+function db_set_comedor_menu_types(string $centro_id, string $aulario_id, array $types): void
+{
+ db()->prepare(
+ 'INSERT OR REPLACE INTO comedor_menu_types (org_id, aulario_id, data) VALUES (?, ?, ?)'
+ )->execute([$centro_id, $aulario_id, json_encode($types, JSON_UNESCAPED_UNICODE)]);
+}
+
+function db_get_comedor_entry(string $centro_id, string $aulario_id, string $ym, string $day): array
+{
+ $stmt = db()->prepare(
+ 'SELECT data FROM comedor_entries WHERE org_id = ? AND aulario_id = ? AND year_month = ? AND day = ?'
+ );
+ $stmt->execute([$centro_id, $aulario_id, $ym, $day]);
+ $row = $stmt->fetch();
+ if ($row === false) {
+ return [];
+ }
+ return json_decode($row['data'], true) ?: [];
+}
+
+function db_set_comedor_entry(string $centro_id, string $aulario_id, string $ym, string $day, array $data): void
+{
+ db()->prepare(
+ 'INSERT OR REPLACE INTO comedor_entries (org_id, aulario_id, year_month, day, data) VALUES (?, ?, ?, ?, ?)'
+ )->execute([$centro_id, $aulario_id, $ym, $day, json_encode($data, JSON_UNESCAPED_UNICODE)]);
+}
+
+// ── Diario helpers ────────────────────────────────────────────────────────────
+
+function db_get_diario_entry(string $centro_id, string $aulario_id, string $entry_date): array
+{
+ $stmt = db()->prepare(
+ 'SELECT data FROM diario_entries WHERE org_id = ? AND aulario_id = ? AND entry_date = ?'
+ );
+ $stmt->execute([$centro_id, $aulario_id, $entry_date]);
+ $row = $stmt->fetch();
+ if ($row === false) {
+ return [];
+ }
+ return json_decode($row['data'], true) ?: [];
+}
+
+function db_set_diario_entry(string $centro_id, string $aulario_id, string $entry_date, array $data): void
+{
+ db()->prepare(
+ 'INSERT OR REPLACE INTO diario_entries (org_id, aulario_id, entry_date, data) VALUES (?, ?, ?, ?)'
+ )->execute([$centro_id, $aulario_id, $entry_date, json_encode($data, JSON_UNESCAPED_UNICODE)]);
+}
+
+// ── Panel alumno helpers ──────────────────────────────────────────────────────
+
+function db_get_panel_alumno(string $centro_id, string $aulario_id, string $alumno): array
+{
+ $stmt = db()->prepare(
+ 'SELECT data FROM panel_alumno WHERE org_id = ? AND aulario_id = ? AND alumno = ?'
+ );
+ $stmt->execute([$centro_id, $aulario_id, $alumno]);
+ $row = $stmt->fetch();
+ if ($row === false) {
+ return [];
+ }
+ return json_decode($row['data'], true) ?: [];
+}
+
+function db_set_panel_alumno(string $centro_id, string $aulario_id, string $alumno, array $data): void
+{
+ db()->prepare(
+ 'INSERT OR REPLACE INTO panel_alumno (org_id, aulario_id, alumno, data) VALUES (?, ?, ?, ?)'
+ )->execute([$centro_id, $aulario_id, $alumno, json_encode($data, JSON_UNESCAPED_UNICODE)]);
+}
+
+// ── Invitation helpers ────────────────────────────────────────────────────────
+
+function db_get_all_invitations(): array
+{
+ return db()->query('SELECT * FROM invitations ORDER BY code')->fetchAll();
+}
+
+function db_get_invitation(string $code): ?array
+{
+ $stmt = db()->prepare('SELECT * FROM invitations WHERE code = ?');
+ $stmt->execute([strtoupper($code)]);
+ $row = $stmt->fetch();
+ return $row !== false ? $row : null;
+}
+
+function db_upsert_invitation(string $code, bool $active, bool $single_use): void
+{
+ db()->prepare(
+ 'INSERT OR REPLACE INTO invitations (code, active, single_use) VALUES (?, ?, ?)'
+ )->execute([strtoupper($code), (int) $active, (int) $single_use]);
+}
+
+function db_deactivate_invitation(string $code): void
+{
+ db()->prepare('UPDATE invitations SET active = 0 WHERE code = ?')->execute([strtoupper($code)]);
+}
+
+function db_delete_invitation(string $code): void
+{
+ db()->prepare('DELETE FROM invitations WHERE code = ?')->execute([strtoupper($code)]);
+}
+
+// ── Club helpers ──────────────────────────────────────────────────────────────
+
+function db_get_club_config(): array
+{
+ $stmt = db()->query('SELECT data FROM club_config WHERE id = 1');
+ $row = $stmt->fetch();
+ if ($row === false) {
+ return [];
+ }
+ return json_decode($row['data'], true) ?: [];
+}
+
+function db_set_club_config(array $config): void
+{
+ db()->prepare('INSERT OR REPLACE INTO club_config (id, data) VALUES (1, ?)')
+ ->execute([json_encode($config, JSON_UNESCAPED_UNICODE)]);
+}
+
+function db_get_all_club_events(): array
+{
+ return db()->query('SELECT date_ref, data FROM club_events ORDER BY date_ref DESC')->fetchAll();
+}
+
+function db_get_club_event(string $date_ref): array
+{
+ $stmt = db()->prepare('SELECT data FROM club_events WHERE date_ref = ?');
+ $stmt->execute([$date_ref]);
+ $row = $stmt->fetch();
+ if ($row === false) {
+ return [];
+ }
+ return json_decode($row['data'], true) ?: [];
+}
+
+function db_set_club_event(string $date_ref, array $data): void
+{
+ db()->prepare('INSERT OR REPLACE INTO club_events (date_ref, data) VALUES (?, ?)')
+ ->execute([$date_ref, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)]);
+}
+
+// ── Multi-tenant helpers ──────────────────────────────────────────────────────
+
+/** Return all organization IDs the authenticated user belongs to. */
+function get_user_organizations(?array $auth_data = null): array
+{
+ $data = $auth_data ?? $_SESSION['auth_data'] ?? [];
+ $orgs = $data['organizations']
+ ?? $data['orgs']
+ ?? $data['aulatek']['organizaciones']
+ ?? $data['aulatek']['organizations']
+ ?? $data['aulatek']['centros']
+ ?? $data['entreaulas']['organizaciones']
+ ?? $data['entreaulas']['organizations']
+ ?? $data['entreaulas']['centros']
+ ?? [];
+
+ if (!empty($orgs) && is_array($orgs)) {
+ return array_values(array_unique(array_filter($orgs, static function ($value): bool {
+ return is_string($value) && $value !== '';
+ })));
+ }
+ if (!empty($orgs)) {
+ return [(string) $orgs];
+ }
+
+ foreach ([
+ $data['active_organization'] ?? null,
+ $data['aulatek']['organizacion'] ?? null,
+ $data['aulatek']['organization'] ?? null,
+ $data['aulatek']['centro'] ?? null,
+ $data['entreaulas']['organizacion'] ?? null,
+ $data['entreaulas']['organization'] ?? null,
+ $data['entreaulas']['centro'] ?? null,
+ ] as $single) {
+ if (is_string($single) && $single !== '') {
+ return [$single];
+ }
+ }
+
+ return [];
+}
+
+/** Spanish alias used by pre-body.php menu rendering. */
+function get_user_organizaciones(?array $auth_data = null): array
+{
+ $org_ids = get_user_organizations($auth_data);
+ if (empty($org_ids)) {
+ return [];
+ }
+ $name_by_id = [];
+ foreach (db_get_organizations() as $org_row) {
+ $name_by_id[$org_row['org_id']] = $org_row['org_name'];
+ }
+
+ $result = [];
+ foreach ($org_ids as $org_id) {
+ $result[$org_id] = $name_by_id[$org_id] ?? $org_id;
+ }
+ return $result;
+}
+
+function get_user_centros(?array $auth_data = null): array
+{
+ return get_user_organizations($auth_data);
+}
+
+/** Ensure active organization session keys are set and mirrored for legacy code. */
+function init_active_org(?array $auth_data = null): void
+{
+ $organizations = get_user_organizations($auth_data);
+ if (empty($organizations)) {
+ $_SESSION['active_organization'] = null;
+ $_SESSION['active_organizacion'] = null;
+ $_SESSION['active_centro'] = null;
+ return;
+ }
+
+ $current = $_SESSION['active_organization']
+ ?? $_SESSION['active_organizacion']
+ ?? $_SESSION['active_centro']
+ ?? null;
+
+ if (!is_string($current) || !in_array($current, $organizations, true)) {
+ $current = $organizations[0];
+ }
+
+ $_SESSION['active_organization'] = $current;
+ $_SESSION['active_organizacion'] = $current;
+ $_SESSION['active_centro'] = $current;
+
+ if (!isset($_SESSION['auth_data']) || !is_array($_SESSION['auth_data'])) {
+ $_SESSION['auth_data'] = [];
+ }
+ $_SESSION['auth_data']['active_organization'] = $current;
+ if (!isset($_SESSION['auth_data']['aulatek']) || !is_array($_SESSION['auth_data']['aulatek'])) {
+ $_SESSION['auth_data']['aulatek'] = [];
+ }
+ $_SESSION['auth_data']['aulatek']['organizacion'] = $current;
+ $_SESSION['auth_data']['aulatek']['organization'] = $current;
+ $_SESSION['auth_data']['aulatek']['centro'] = $current;
+ if (!isset($_SESSION['auth_data']['entreaulas']) || !is_array($_SESSION['auth_data']['entreaulas'])) {
+ $_SESSION['auth_data']['entreaulas'] = [];
+ }
+ $_SESSION['auth_data']['entreaulas']['organizacion'] = $current;
+ $_SESSION['auth_data']['entreaulas']['organization'] = $current;
+ $_SESSION['auth_data']['entreaulas']['centro'] = $current;
+}
+
+function init_active_centro(?array $auth_data = null): void
+{
+ init_active_org($auth_data);
+}
diff --git a/public_html/_incl/migrations/001_initial_schema.sql b/public_html/_incl/migrations/001_initial_schema.sql
new file mode 100644
index 0000000..a73a859
--- /dev/null
+++ b/public_html/_incl/migrations/001_initial_schema.sql
@@ -0,0 +1,144 @@
+-- Axia4 Migration 001: Initial Schema
+-- Converts all JSON file-based storage to a proper relational schema.
+
+PRAGMA journal_mode = WAL;
+PRAGMA foreign_keys = ON;
+
+-- ── Application configuration (replaces /DATA/AuthConfig.json) ─────────────
+CREATE TABLE IF NOT EXISTS config (
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL DEFAULT ''
+);
+
+-- ── System installation flag (replaces /DATA/SISTEMA_INSTALADO.txt) ────────
+-- Stored as a config row: key='installed', value='1'
+
+-- ── Users (replaces /DATA/Usuarios/*.json) ─────────────────────────────────
+CREATE TABLE IF NOT EXISTS users (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username TEXT UNIQUE NOT NULL,
+ display_name TEXT NOT NULL DEFAULT '',
+ email TEXT NOT NULL DEFAULT '',
+ password_hash TEXT NOT NULL DEFAULT '',
+ permissions TEXT NOT NULL DEFAULT '[]', -- JSON array
+ google_auth INTEGER NOT NULL DEFAULT 0,
+ meta TEXT NOT NULL DEFAULT '{}', -- JSON for extra fields
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
+);
+
+-- ── Invitations (replaces /DATA/Invitaciones_de_usuarios.json) ─────────────
+CREATE TABLE IF NOT EXISTS invitations (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ code TEXT UNIQUE NOT NULL,
+ active INTEGER NOT NULL DEFAULT 1,
+ single_use INTEGER NOT NULL DEFAULT 1,
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+);
+
+-- ── Centros/Organizations ───────────────────────────────────────────────────
+-- Replaces directory existence at /DATA/entreaulas/Centros/{centro_id}/
+CREATE TABLE IF NOT EXISTS centros (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ centro_id TEXT UNIQUE NOT NULL,
+ name TEXT NOT NULL DEFAULT '',
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+);
+
+-- ── User ↔ Centro assignments (many-to-many) ───────────────────────────────
+-- Replaces entreaulas.centro + entreaulas.aulas fields in user JSON.
+-- A single user can belong to multiple centros (multi-tenant).
+CREATE TABLE IF NOT EXISTS user_centros (
+ user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE,
+ role TEXT NOT NULL DEFAULT '',
+ aulas TEXT NOT NULL DEFAULT '[]', -- JSON array of aulario_ids
+ PRIMARY KEY (user_id, centro_id)
+);
+
+-- ── Aularios (replaces /DATA/entreaulas/Centros/{}/Aularios/{id}.json) ──────
+CREATE TABLE IF NOT EXISTS aularios (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE,
+ aulario_id TEXT NOT NULL,
+ name TEXT NOT NULL DEFAULT '',
+ icon TEXT NOT NULL DEFAULT '',
+ extra TEXT NOT NULL DEFAULT '{}', -- JSON for extra config
+ UNIQUE (centro_id, aulario_id)
+);
+
+-- ── SuperCafe menu (replaces .../SuperCafe/Menu.json) ──────────────────────
+CREATE TABLE IF NOT EXISTS supercafe_menu (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE,
+ data TEXT NOT NULL DEFAULT '{}', -- JSON matching existing format
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
+ UNIQUE (centro_id)
+);
+
+-- ── SuperCafe orders (replaces .../SuperCafe/Comandas/*.json) ───────────────
+CREATE TABLE IF NOT EXISTS supercafe_orders (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE,
+ order_ref TEXT NOT NULL,
+ fecha TEXT NOT NULL,
+ persona TEXT NOT NULL,
+ comanda TEXT NOT NULL DEFAULT '',
+ notas TEXT NOT NULL DEFAULT '',
+ estado TEXT NOT NULL DEFAULT 'Pedido',
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
+ UNIQUE (centro_id, order_ref)
+);
+
+-- ── Comedor menu types (replaces .../Comedor-MenuTypes.json) ────────────────
+CREATE TABLE IF NOT EXISTS comedor_menu_types (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE,
+ aulario_id TEXT NOT NULL,
+ data TEXT NOT NULL DEFAULT '[]', -- JSON array of menu type objs
+ UNIQUE (centro_id, aulario_id)
+);
+
+-- ── Comedor daily entries (replaces .../Comedor/{ym}/{day}/_datos.json) ─────
+CREATE TABLE IF NOT EXISTS comedor_entries (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE,
+ aulario_id TEXT NOT NULL,
+ year_month TEXT NOT NULL, -- "2024-01"
+ day TEXT NOT NULL, -- "15"
+ data TEXT NOT NULL DEFAULT '{}',
+ UNIQUE (centro_id, aulario_id, year_month, day)
+);
+
+-- ── Diary entries (replaces .../Diario/*.json) ──────────────────────────────
+CREATE TABLE IF NOT EXISTS diario_entries (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE,
+ aulario_id TEXT NOT NULL,
+ entry_date TEXT NOT NULL,
+ data TEXT NOT NULL DEFAULT '{}',
+ UNIQUE (centro_id, aulario_id, entry_date)
+);
+
+-- ── Panel diario per-student data (replaces .../Alumnos/*/Panel.json) ───────
+CREATE TABLE IF NOT EXISTS panel_alumno (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ centro_id TEXT NOT NULL REFERENCES centros(centro_id) ON DELETE CASCADE,
+ aulario_id TEXT NOT NULL,
+ alumno TEXT NOT NULL,
+ data TEXT NOT NULL DEFAULT '{}',
+ UNIQUE (centro_id, aulario_id, alumno)
+);
+
+-- ── Club event metadata (replaces /DATA/club/IMG/{date}/data.json) ──────────
+CREATE TABLE IF NOT EXISTS club_events (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ date_ref TEXT UNIQUE NOT NULL,
+ data TEXT NOT NULL DEFAULT '{}'
+);
+
+-- ── Club configuration (replaces /DATA/club/config.json) ────────────────────
+CREATE TABLE IF NOT EXISTS club_config (
+ id INTEGER PRIMARY KEY CHECK (id = 1),
+ data TEXT NOT NULL DEFAULT '{}'
+);
diff --git a/public_html/_incl/migrations/002_import_json.php b/public_html/_incl/migrations/002_import_json.php
new file mode 100644
index 0000000..869c674
--- /dev/null
+++ b/public_html/_incl/migrations/002_import_json.php
@@ -0,0 +1,256 @@
+prepare("INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)");
+ foreach ($auth_config as $k => $v) {
+ $ins->execute([$k, is_string($v) ? $v : json_encode($v)]);
+ }
+}
+
+// ── SISTEMA_INSTALADO marker ─────────────────────────────────────────────────
+if (file_exists('/DATA/SISTEMA_INSTALADO.txt')) {
+ $db->prepare("INSERT OR IGNORE INTO config (key, value) VALUES ('installed', '1')")->execute();
+}
+
+// ── Users (/DATA/Usuarios/*.json) ────────────────────────────────────────────
+$users_dir = '/DATA/Usuarios';
+if (is_dir($users_dir)) {
+ $ins_user = $db->prepare(
+ "INSERT OR IGNORE INTO users
+ (username, display_name, email, password_hash, permissions, google_auth, meta)
+ VALUES (?, ?, ?, ?, ?, ?, ?)"
+ );
+ $ins_uc = $db->prepare(
+ "INSERT OR IGNORE INTO user_centros (user_id, centro_id, role, aulas)
+ VALUES (?, ?, ?, ?)"
+ );
+ $ins_centro = $db->prepare("INSERT OR IGNORE INTO centros (centro_id) VALUES (?)");
+
+ foreach (glob("$users_dir/*.json") ?: [] as $user_file) {
+ $username = basename($user_file, '.json');
+ $data = json_decode(file_get_contents($user_file), true);
+ if (!is_array($data)) {
+ continue;
+ }
+ $permissions = isset($data['permissions']) ? json_encode($data['permissions']) : '[]';
+ // Store remaining non-standard keys in meta
+ $meta_keys = ['display_name', 'email', 'password_hash', 'permissions', 'entreaulas', 'google_auth'];
+ $meta = [];
+ foreach ($data as $k => $v) {
+ if (!in_array($k, $meta_keys, true)) {
+ $meta[$k] = $v;
+ }
+ }
+ $ins_user->execute([
+ $username,
+ $data['display_name'] ?? '',
+ $data['email'] ?? '',
+ $data['password_hash'] ?? '',
+ $permissions,
+ (int) ($data['google_auth'] ?? 0),
+ json_encode($meta),
+ ]);
+ $user_id = (int) $db->lastInsertId();
+ if ($user_id === 0) {
+ // Already existed – look it up
+ $stmt2 = $db->prepare("SELECT id FROM users WHERE username = ?");
+ $stmt2->execute([$username]);
+ $user_id = (int) $stmt2->fetchColumn();
+ }
+
+ // Entreaulas centro assignment
+ $ea = $data['entreaulas'] ?? [];
+ // Support both old single "centro" and new "centros" array
+ $centros = [];
+ if (!empty($ea['centros']) && is_array($ea['centros'])) {
+ $centros = $ea['centros'];
+ } elseif (!empty($ea['centro'])) {
+ $centros = [$ea['centro']];
+ }
+ $role = $ea['role'] ?? '';
+ $aulas = json_encode($ea['aulas'] ?? []);
+ foreach ($centros as $cid) {
+ if ($cid === '') {
+ continue;
+ }
+ $ins_centro->execute([$cid]);
+ $ins_uc->execute([$user_id, $cid, $role, $aulas]);
+ }
+ }
+}
+
+// ── Invitations (/DATA/Invitaciones_de_usuarios.json) ────────────────────────
+$inv_file = '/DATA/Invitaciones_de_usuarios.json';
+if (file_exists($inv_file)) {
+ $invs = json_decode(file_get_contents($inv_file), true) ?? [];
+ $ins = $db->prepare(
+ "INSERT OR IGNORE INTO invitations (code, active, single_use) VALUES (?, ?, ?)"
+ );
+ foreach ($invs as $code => $inv) {
+ $ins->execute([
+ strtoupper($code),
+ (int) ($inv['active'] ?? 1),
+ (int) ($inv['single_use'] ?? 1),
+ ]);
+ }
+}
+
+// ── Centros & Aularios (directory structure) ──────────────────────────────────
+$centros_base = '/DATA/entreaulas/Centros';
+if (is_dir($centros_base)) {
+ $ins_centro = $db->prepare("INSERT OR IGNORE INTO centros (centro_id) VALUES (?)");
+ $ins_aulario = $db->prepare(
+ "INSERT OR IGNORE INTO aularios (centro_id, aulario_id, name, icon, extra) VALUES (?, ?, ?, ?, ?)"
+ );
+ foreach (glob("$centros_base/*", GLOB_ONLYDIR) ?: [] as $centro_dir) {
+ $centro_id = basename($centro_dir);
+ $ins_centro->execute([$centro_id]);
+
+ $aularios_dir = "$centro_dir/Aularios";
+ foreach (glob("$aularios_dir/*.json") ?: [] as $aulario_file) {
+ $aulario_id = basename($aulario_file, '.json');
+ $adata = json_decode(file_get_contents($aulario_file), true);
+ if (!is_array($adata)) {
+ continue;
+ }
+ $name = $adata['name'] ?? $aulario_id;
+ $icon = $adata['icon'] ?? '';
+ $extra_keys = ['name', 'icon'];
+ $extra = [];
+ foreach ($adata as $k => $v) {
+ if (!in_array($k, $extra_keys, true)) {
+ $extra[$k] = $v;
+ }
+ }
+ $ins_aulario->execute([$centro_id, $aulario_id, $name, $icon, json_encode($extra)]);
+ }
+
+ // SuperCafe menu
+ $menu_file = "$centro_dir/SuperCafe/Menu.json";
+ if (file_exists($menu_file)) {
+ $menu_data = file_get_contents($menu_file);
+ $db->prepare("INSERT OR IGNORE INTO supercafe_menu (centro_id, data) VALUES (?, ?)")
+ ->execute([$centro_id, $menu_data]);
+ }
+
+ // SuperCafe orders
+ $comandas_dir = "$centro_dir/SuperCafe/Comandas";
+ if (is_dir($comandas_dir)) {
+ $ins_order = $db->prepare(
+ "INSERT OR IGNORE INTO supercafe_orders
+ (centro_id, order_ref, fecha, persona, comanda, notas, estado)
+ VALUES (?, ?, ?, ?, ?, ?, ?)"
+ );
+ foreach (glob("$comandas_dir/*.json") ?: [] as $order_file) {
+ $order_ref = basename($order_file, '.json');
+ $odata = json_decode(file_get_contents($order_file), true);
+ if (!is_array($odata)) {
+ continue;
+ }
+ $ins_order->execute([
+ $centro_id,
+ $order_ref,
+ $odata['Fecha'] ?? '',
+ $odata['Persona'] ?? '',
+ $odata['Comanda'] ?? '',
+ $odata['Notas'] ?? '',
+ $odata['Estado'] ?? 'Pedido',
+ ]);
+ }
+ }
+
+ // Comedor menu types & daily entries per aulario
+ foreach (glob("$aularios_dir/*.json") ?: [] as $aulario_file) {
+ $aulario_id = basename($aulario_file, '.json');
+
+ $menu_types_file = "$aularios_dir/$aulario_id/Comedor-MenuTypes.json";
+ if (file_exists($menu_types_file)) {
+ $db->prepare(
+ "INSERT OR IGNORE INTO comedor_menu_types (centro_id, aulario_id, data) VALUES (?, ?, ?)"
+ )->execute([$centro_id, $aulario_id, file_get_contents($menu_types_file)]);
+ }
+
+ $comedor_base = "$aularios_dir/$aulario_id/Comedor";
+ if (is_dir($comedor_base)) {
+ $ins_centry = $db->prepare(
+ "INSERT OR IGNORE INTO comedor_entries (centro_id, aulario_id, year_month, day, data) VALUES (?, ?, ?, ?, ?)"
+ );
+ foreach (glob("$comedor_base/*", GLOB_ONLYDIR) ?: [] as $ym_dir) {
+ $ym = basename($ym_dir);
+ foreach (glob("$ym_dir/*", GLOB_ONLYDIR) ?: [] as $day_dir) {
+ $day = basename($day_dir);
+ $data_file = "$day_dir/_datos.json";
+ if (file_exists($data_file)) {
+ $ins_centry->execute([
+ $centro_id, $aulario_id, $ym, $day,
+ file_get_contents($data_file),
+ ]);
+ }
+ }
+ }
+ }
+
+ // Diario entries
+ $diario_base = "$aularios_dir/$aulario_id/Diario";
+ if (is_dir($diario_base)) {
+ $ins_d = $db->prepare(
+ "INSERT OR IGNORE INTO diario_entries (centro_id, aulario_id, entry_date, data) VALUES (?, ?, ?, ?)"
+ );
+ foreach (glob("$diario_base/*.json") ?: [] as $diario_file) {
+ $entry_date = basename($diario_file, '.json');
+ $ins_d->execute([$centro_id, $aulario_id, $entry_date, file_get_contents($diario_file)]);
+ }
+ }
+
+ // Panel alumno data
+ $alumnos_base = "$aularios_dir/$aulario_id/Alumnos";
+ if (is_dir($alumnos_base)) {
+ $ins_pa = $db->prepare(
+ "INSERT OR IGNORE INTO panel_alumno (centro_id, aulario_id, alumno, data) VALUES (?, ?, ?, ?)"
+ );
+ foreach (glob("$alumnos_base/*/", GLOB_ONLYDIR) ?: [] as $alumno_dir) {
+ $alumno = basename($alumno_dir);
+ // Look for Panel.json (used by paneldiario)
+ $panel_files = glob("$alumno_dir/Panel*.json") ?: [];
+ foreach ($panel_files as $pf) {
+ $ins_pa->execute([
+ $centro_id, $aulario_id, $alumno,
+ file_get_contents($pf),
+ ]);
+ }
+ }
+ }
+ }
+ }
+}
+
+// ── Club config (/DATA/club/config.json) ──────────────────────────────────────
+$club_config_file = '/DATA/club/config.json';
+if (file_exists($club_config_file)) {
+ $db->prepare("INSERT OR IGNORE INTO club_config (id, data) VALUES (1, ?)")
+ ->execute([file_get_contents($club_config_file)]);
+}
+
+// ── Club events (/DATA/club/IMG/{date}/data.json) ─────────────────────────────
+$club_img_dir = '/DATA/club/IMG';
+if (is_dir($club_img_dir)) {
+ $ins_ev = $db->prepare("INSERT OR IGNORE INTO club_events (date_ref, data) VALUES (?, ?)");
+ foreach (glob("$club_img_dir/*/", GLOB_ONLYDIR) ?: [] as $event_dir) {
+ $date_ref = basename($event_dir);
+ $event_data_file = "$event_dir/data.json";
+ $ins_ev->execute([
+ $date_ref,
+ file_exists($event_data_file) ? file_get_contents($event_data_file) : '{}',
+ ]);
+ }
+}
diff --git a/public_html/_incl/migrations/003_organizaciones.sql b/public_html/_incl/migrations/003_organizaciones.sql
new file mode 100644
index 0000000..a526e72
--- /dev/null
+++ b/public_html/_incl/migrations/003_organizaciones.sql
@@ -0,0 +1,163 @@
+-- filepath: /workspaces/Axia4/public_html/_incl/migrations/003_organizaciones.sql
+-- Axia4 Migration 003: Rename centros to organizaciones
+-- Migrates the centros table to organizaciones with org_id and org_name columns.
+
+PRAGMA journal_mode = WAL;
+PRAGMA foreign_keys = ON;
+
+-- ── Create new organizaciones table ──────────────────────────────────────────
+CREATE TABLE IF NOT EXISTS organizaciones (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ org_id TEXT UNIQUE NOT NULL,
+ org_name TEXT NOT NULL DEFAULT '',
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+);
+
+-- ── Migrate data from centros to organizaciones ──────────────────────────────
+INSERT INTO organizaciones (org_id, org_name, created_at)
+SELECT centro_id, COALESCE(name, centro_id), created_at
+FROM centros
+WHERE NOT EXISTS (
+ SELECT 1 FROM organizaciones WHERE org_id = centros.centro_id
+);
+
+-- ── Update foreign key references in user_centros ──────────────────────────────
+-- user_centros.centro_id → user_centros.org_id (rename column if needed via recreation)
+-- For SQLite, we need to recreate the table due to FK constraint changes
+
+CREATE TABLE user_centros_new (
+ user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
+ role TEXT NOT NULL DEFAULT '',
+ ea_aulas TEXT NOT NULL DEFAULT '[]',
+ PRIMARY KEY (user_id, org_id)
+);
+
+INSERT INTO user_centros_new (user_id, org_id, role, ea_aulas)
+SELECT user_id, centro_id, role, aulas FROM user_centros;
+
+DROP TABLE user_centros;
+ALTER TABLE user_centros_new RENAME TO user_orgs;
+
+-- ── Update foreign key references in aularios ──────────────────────────────────
+CREATE TABLE aularios_new (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
+ aulario_id TEXT NOT NULL,
+ name TEXT NOT NULL DEFAULT '',
+ icon TEXT NOT NULL DEFAULT '',
+ extra TEXT NOT NULL DEFAULT '{}',
+ UNIQUE (org_id, aulario_id)
+);
+
+INSERT INTO aularios_new (id, org_id, aulario_id, name, icon, extra)
+SELECT id, centro_id, aulario_id, name, icon, extra FROM aularios;
+
+DROP TABLE aularios;
+ALTER TABLE aularios_new RENAME TO aularios;
+
+-- ── Update foreign key references in remaining tables ──────────────────────────
+-- supercafe_menu
+CREATE TABLE supercafe_menu_new (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
+ data TEXT NOT NULL DEFAULT '{}',
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
+ UNIQUE (org_id)
+);
+
+INSERT INTO supercafe_menu_new (id, org_id, data, updated_at)
+SELECT id, centro_id, data, updated_at FROM supercafe_menu;
+
+DROP TABLE supercafe_menu;
+ALTER TABLE supercafe_menu_new RENAME TO supercafe_menu;
+
+-- supercafe_orders
+CREATE TABLE supercafe_orders_new (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
+ order_ref TEXT NOT NULL,
+ fecha TEXT NOT NULL,
+ persona TEXT NOT NULL,
+ comanda TEXT NOT NULL DEFAULT '',
+ notas TEXT NOT NULL DEFAULT '',
+ estado TEXT NOT NULL DEFAULT 'Pedido',
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
+ UNIQUE (org_id, order_ref)
+);
+
+INSERT INTO supercafe_orders_new (id, org_id, order_ref, fecha, persona, comanda, notas, estado, created_at)
+SELECT id, centro_id, order_ref, fecha, persona, comanda, notas, estado, created_at FROM supercafe_orders;
+
+DROP TABLE supercafe_orders;
+ALTER TABLE supercafe_orders_new RENAME TO supercafe_orders;
+
+-- comedor_menu_types
+CREATE TABLE comedor_menu_types_new (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
+ aulario_id TEXT NOT NULL,
+ data TEXT NOT NULL DEFAULT '[]',
+ UNIQUE (org_id, aulario_id)
+);
+
+INSERT INTO comedor_menu_types_new (id, org_id, aulario_id, data)
+SELECT id, centro_id, aulario_id, data FROM comedor_menu_types;
+
+DROP TABLE comedor_menu_types;
+ALTER TABLE comedor_menu_types_new RENAME TO comedor_menu_types;
+
+-- comedor_entries
+CREATE TABLE comedor_entries_new (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
+ aulario_id TEXT NOT NULL,
+ year_month TEXT NOT NULL,
+ day TEXT NOT NULL,
+ data TEXT NOT NULL DEFAULT '{}',
+ UNIQUE (org_id, aulario_id, year_month, day)
+);
+
+INSERT INTO comedor_entries_new (id, org_id, aulario_id, year_month, day, data)
+SELECT id, centro_id, aulario_id, year_month, day, data FROM comedor_entries;
+
+DROP TABLE comedor_entries;
+ALTER TABLE comedor_entries_new RENAME TO comedor_entries;
+
+-- diario_entries
+CREATE TABLE diario_entries_new (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
+ aulario_id TEXT NOT NULL,
+ entry_date TEXT NOT NULL,
+ data TEXT NOT NULL DEFAULT '{}',
+ UNIQUE (org_id, aulario_id, entry_date)
+);
+
+INSERT INTO diario_entries_new (id, org_id, aulario_id, entry_date, data)
+SELECT id, centro_id, aulario_id, entry_date, data FROM diario_entries;
+
+DROP TABLE diario_entries;
+ALTER TABLE diario_entries_new RENAME TO diario_entries;
+
+-- panel_alumno
+CREATE TABLE panel_alumno_new (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ org_id TEXT NOT NULL REFERENCES organizaciones(org_id) ON DELETE CASCADE,
+ aulario_id TEXT NOT NULL,
+ alumno TEXT NOT NULL,
+ data TEXT NOT NULL DEFAULT '{}',
+ UNIQUE (org_id, aulario_id, alumno)
+);
+
+INSERT INTO panel_alumno_new (id, org_id, aulario_id, alumno, data)
+SELECT id, centro_id, aulario_id, alumno, data FROM panel_alumno;
+
+DROP TABLE panel_alumno;
+ALTER TABLE panel_alumno_new RENAME TO panel_alumno;
+
+-- ── Drop old centros table ─────────────────────────────────────────────────────
+DROP TABLE IF EXISTS centros;
+
+-- ── Verify migration ───────────────────────────────────────────────────────────
+-- SELECT COUNT(*) as total_organizaciones FROM organizaciones;
\ No newline at end of file
diff --git a/public_html/_incl/pre-body.php b/public_html/_incl/pre-body.php
index d7b6592..9f83c1b 100755
--- a/public_html/_incl/pre-body.php
+++ b/public_html/_incl/pre-body.php
@@ -27,6 +27,12 @@ if (!empty($displayName)) {
$initials = mb_strtoupper($first . $last);
}
+// Tenant (organización) management
+$userOrganizaciones = get_user_organizaciones($_SESSION["auth_data"] ?? []);
+$activeOrganizacionId = $_SESSION['active_organizacion']
+ ?? ($_SESSION["auth_data"]["aulatek"]["organizacion"] ?? ($_SESSION["auth_data"]["entreaulas"]["organizacion"] ?? ''));
+$activeOrganizacionName = $userOrganizaciones[$activeOrganizacionId] ?? '';
+
?>
@@ -38,412 +44,362 @@ if (!empty($displayName)) {
" />
+
+
+
@@ -548,96 +471,116 @@ if (!empty($displayName)) {
});
})();
+
+
+
+
+
-
-
300) {
- $username = $_SESSION["auth_user"];
- $user_filename = safe_username_to_filename($username);
- if ($user_filename !== "") {
- $userdata = json_decode(file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true);
- $_SESSION["auth_data"] = $userdata;
- }
- $_SESSION["last_reload_time"] = time();
+ } elseif ($load_mode !== "never") {
+ $last = $_SESSION["last_reload_time"] ?? 0;
+ if (time() - $last > 300) {
+ $row = db_get_user($_SESSION["auth_user"]);
+ if ($row) {
+ $_SESSION["auth_data"] = db_build_auth_data($row);
+ init_active_org($_SESSION["auth_data"]);
}
- } else {
+ $_SESSION["last_reload_time"] = time();
+ }
+ if (!isset($_SESSION["last_reload_time"])) {
$_SESSION["last_reload_time"] = time();
}
}
}
-
-function user_is_authenticated()
+function user_is_authenticated(): bool
{
return isset($_SESSION["auth_ok"]) && $_SESSION["auth_ok"] === true;
}
-function user_has_permission($perm)
+
+function user_has_permission(string $perm): bool
{
- return in_array($perm, $_SESSION["auth_data"]["permissions"] ?? []);
+ return in_array($perm, $_SESSION["auth_data"]["permissions"] ?? [], true);
}
diff --git a/public_html/_incl/tools.security.php b/public_html/_incl/tools.security.php
index dd80800..1d55a4d 100644
--- a/public_html/_incl/tools.security.php
+++ b/public_html/_incl/tools.security.php
@@ -108,12 +108,23 @@ function Sb($input) {
}
function get_user_file_path($username)
{
- return USERS_DIR . $username . '.json';
+ $users_dir = defined('USERS_DIR') ? USERS_DIR : '/DATA/Usuarios/';
+ return rtrim($users_dir, '/') . '/' . $username . '.json';
+}
+
+function safe_organization_id($value)
+{
+ return preg_replace('/[^a-zA-Z0-9._-]/', '', (string)$value);
+}
+
+function safe_organizacion_id($value)
+{
+ return safe_organization_id($value);
}
function safe_centro_id($value)
{
- return preg_replace('/[^a-zA-Z0-9._-]/', '', (string)$value);
+ return safe_organization_id($value);
}
function safe_aulario_id($value)
@@ -156,12 +167,42 @@ function path_is_within($real_base, $real_path)
return strpos($real_path, $base_prefix) === 0 || $real_path === rtrim($real_base, DIRECTORY_SEPARATOR);
}
+function aulatek_orgs_base_path()
+{
+ $orgs_path = '/DATA/entreaulas/Organizaciones';
+ $legacy_path = '/DATA/entreaulas/Centros';
+ if (is_dir($orgs_path)) {
+ return $orgs_path;
+ }
+ if (is_dir($legacy_path)) {
+ return $legacy_path;
+ }
+ return $orgs_path;
+}
+
+function entreaulas_orgs_base_path()
+{
+ return aulatek_orgs_base_path();
+}
+
function safe_aulario_config_path($centro_id, $aulario_id)
{
- $centro = safe_centro_id($centro_id);
+ $centro = safe_organization_id($centro_id);
$aulario = safe_id_segment($aulario_id);
if ($centro === '' || $aulario === '') {
return null;
}
- return "/DATA/entreaulas/Centros/$centro/Aularios/$aulario.json";
+ return aulatek_orgs_base_path() . "/$centro/Aularios/$aulario.json";
+}
+
+function safe_redir($url, $default = "/")
+{
+ if (empty($url) || !is_string($url)) {
+ return $default;
+ }
+ // Only allow relative URLs that start with /
+ if (str_starts_with($url, "/") && !str_contains($url, "\0")) {
+ return $url;
+ }
+ return $default;
}
\ No newline at end of file
diff --git a/public_html/_install.php b/public_html/_install.php
index 764e6ca..0f67c15 100644
--- a/public_html/_install.php
+++ b/public_html/_install.php
@@ -1,28 +1,25 @@
'Administrador',
- 'email' => "$admin_user@nomail.arpa",
- 'permissions' => ['*', 'sysadmin:access', 'entreaulas:access'],
- 'password_hash' => $password_hash
- ];
- if (!is_dir("/DATA/Usuarios")) {
- mkdir("/DATA/Usuarios", 0777, true);
- }
- file_put_contents("/DATA/Usuarios/$admin_user.json", json_encode($admin_userdata, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
- file_put_contents("/DATA/SISTEMA_INSTALADO.txt", "Sistema instalado el ".date("Y-m-d H:i:s")."\n");
+ db_upsert_user([
+ 'username' => $admin_user,
+ 'display_name' => 'Administrador',
+ 'email' => "$admin_user@nomail.arpa",
+ 'permissions' => ['*', 'sysadmin:access', 'aulatek:access'],
+ 'password_hash' => password_hash($admin_password, PASSWORD_DEFAULT),
+ ]);
+ db_set_config('installed', '1');
header("Location: /_login.php");
exit;
break;
diff --git a/public_html/_login.php b/public_html/_login.php
index 324ac4e..853a30e 100755
--- a/public_html/_login.php
+++ b/public_html/_login.php
@@ -1,38 +1,27 @@
$name,
- "email" => $email,
- "permissions" => ["public"],
- "password_hash" => password_hash($password, PASSWORD_DEFAULT),
- "google_auth" => true,
- "#" => "Este usuario fue creado automáticamente al iniciar sesión con Google por primera vez.",
- ];
- file_put_contents($userfile, json_encode($userdata));
+ db_upsert_user([
+ 'username' => $username,
+ 'display_name' => $name,
+ 'email' => $email,
+ 'permissions' => ['public'],
+ 'password_hash' => password_hash($password, PASSWORD_DEFAULT),
+ 'google_auth' => true,
+ '#' => 'Este usuario fue creado automáticamente al iniciar sesión con Google por primera vez.',
+ ]);
+ $user_row = db_get_user($username);
}
-
+
session_regenerate_id(true);
- $_SESSION['auth_user'] = $email;
- $_SESSION['auth_data'] = $userdata;
- $_SESSION['auth_ok'] = true;
+ $_SESSION['auth_user'] = $username;
+ $_SESSION['auth_data'] = db_build_auth_data($user_row);
+ $_SESSION['auth_ok'] = true;
+ init_active_org($_SESSION['auth_data']);
$cookie_options = ["expires" => time() + (86400 * 30), "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"];
- setcookie("auth_user", $email, $cookie_options);
- setcookie("auth_pass_b64", base64_encode($password), $cookie_options);
+ setcookie("auth_user", $username, $cookie_options);
+ setcookie("auth_pass_b64", base64_encode($password), $cookie_options);
$redir = safe_redir($state["redir"] ?? "/");
header("Location: $redir");
die();
}
-if ($_GET["google"] == "1") {
+if (($_GET["google"] ?? "") === "1") {
if (!isset($AuthConfig["google_client_id"]) || !isset($AuthConfig["google_client_secret"])) {
die("Error: La autenticación de Google no está configurada.");
}
@@ -145,7 +136,7 @@ if ($_GET["google"] == "1") {
header("Location: " . $request_to);
die();
}
-if ($_GET["logout"] == "1") {
+if (($_GET["logout"] ?? "") === "1") {
$redir = safe_redir($_GET["redir"] ?? "/");
$cookie_options_expired = ["expires" => time() - 3600, "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"];
setcookie("auth_user", "", $cookie_options_expired);
@@ -154,27 +145,26 @@ if ($_GET["logout"] == "1") {
header("Location: $redir");
die();
}
-if ($_GET["clear_session"] == "1") {
+if (($_GET["clear_session"] ?? "") === "1") {
session_destroy();
$redir = safe_redir($_GET["redir"] ?? "/");
header("Location: $redir");
die();
}
if (isset($_POST["user"])) {
- $valid = "";
- $user = trim(strtolower($_POST["user"]));
+ $user = trim(strtolower($_POST["user"]));
$password = $_POST["password"];
- $user_filename = safe_username_to_filename($user);
- $userdata = ($user_filename !== "") ? json_decode(@file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true) : null;
- if (!is_array($userdata) || !isset($userdata["password_hash"])) {
+ $row = db_get_user($user);
+ if (!$row || !isset($row["password_hash"])) {
$_GET["_result"] = "El usuario no existe.";
- } elseif (password_verify($password, $userdata["password_hash"])) {
+ } elseif (password_verify($password, $row["password_hash"])) {
session_regenerate_id(true);
$_SESSION['auth_user'] = $user;
- $_SESSION['auth_data'] = $userdata;
- $_SESSION['auth_ok'] = true;
+ $_SESSION['auth_data'] = db_build_auth_data($row);
+ $_SESSION['auth_ok'] = true;
+ init_active_org($_SESSION['auth_data']);
$cookie_options = ["expires" => time() + (86400 * 30), "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"];
- setcookie("auth_user", $user, $cookie_options);
+ setcookie("auth_user", $user, $cookie_options);
setcookie("auth_pass_b64", base64_encode($password), $cookie_options);
$redir = safe_redir($_GET["redir"] ?? "/");
header("Location: $redir");
@@ -182,9 +172,8 @@ if (isset($_POST["user"])) {
} else {
$_GET["_result"] = "La contraseña no es correcta.";
}
-
}
-if (!file_exists("/DATA/SISTEMA_INSTALADO.txt")) {
+if (db_get_config('installed') !== '1') {
header("Location: /_install.php");
die();
}
diff --git a/public_html/account/index.php b/public_html/account/index.php
index 0ea6b70..726b237 100644
--- a/public_html/account/index.php
+++ b/public_html/account/index.php
@@ -1,44 +1,132 @@
-
-
-
¡Hola, !
- Tu Email:
- Tu Nombre de Usuario:
-
-
-
Código QR
-
" alt="QR Code de Nombre de Usuario" style="margin: 0 auto;">
-
Escanea este código para iniciar sesión. Es como tu contraseña, pero más fácil.
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
Mi Perfil
+
= htmlspecialchars($initials) ?>
+
= htmlspecialchars($displayName) ?>
+
= htmlspecialchars($email ?: 'Sin correo') ?>
+
Usuario = htmlspecialchars($username) ?>
+
+
Rol = htmlspecialchars($role) ?>
+
+
+
+
+
+
+
Código QR de Acceso
+
+
Escanea este código para iniciar sesión rápidamente.
+
+
+
+
+
+
Organizaciones
+
+
+
+
+
+
+
+
+
+
Mis Aulas (= htmlspecialchars($activeOrganization) ?>)
+
+
+
+
+
+
+
= htmlspecialchars($aula['name'] ?? $aula_id) ?>
+
Asignada
+
+
+
+
+
+
+
+
+
Permisos
+
+
+ = htmlspecialchars($p) ?>
+
+
+
+
+
+
+
+
Sesión Activa
+
ID Sesión = htmlspecialchars(substr(session_id(), 0, 12)) ?>…
+
Org. activa = htmlspecialchars($activeOrganization ?: '–') ?>
+
Autenticación = empty($authData['google_auth']) ? 'Contraseña' : 'Google' ?>
+
+
+
+
+
+
diff --git a/public_html/account/register.php b/public_html/account/register.php
index 7dbc0f5..1752e34 100644
--- a/public_html/account/register.php
+++ b/public_html/account/register.php
@@ -1,36 +1,27 @@
$_POST['display_name'],
- 'email' => $_POST['email'],
- 'password_hash' => password_hash($_POST['password'], PASSWORD_DEFAULT),
- '_meta_signup' => [
- 'invitation_code' => $invi_code
- ],
- 'permissions' => []
- ];
- if ($invitations[$invi_code]["active"] != true) {
- header("Location: /?_resultcolor=red&_result=" . urlencode("Código de invitación no válido."));
- exit;
- }
- $username = $_POST['username'];
- if (file_exists("/DATA/Usuarios/$username.json")) {
+ $username = strtolower(trim($_POST['username'] ?? ''));
+ if (db_get_user($username)) {
header("Location: /?_resultcolor=red&_result=" . urlencode("El nombre de usuario ya existe. Por favor, elige otro."));
exit;
}
- file_put_contents("/DATA/Usuarios/$username.json", json_encode($userdata, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
- // Deactivate invitation code if it's single-use
- if ($invitations[$invi_code]["single_use"] === true) {
- $invitations[$invi_code]["active"] = false;
- file_put_contents("/DATA/Invitaciones_de_usuarios.json", json_encode($invitations, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
+ db_upsert_user([
+ 'username' => $username,
+ 'display_name' => $_POST['display_name'] ?? '',
+ 'email' => $_POST['email'] ?? '',
+ 'password_hash' => password_hash($_POST['password'], PASSWORD_DEFAULT),
+ 'permissions' => [],
+ '_meta_signup' => ['invitation_code' => $invi_code],
+ ]);
+ if ($invitation['single_use']) {
+ db_deactivate_invitation($invi_code);
}
header("Location: /?_result=" . urlencode("Cuenta creada correctamente. Ya puedes iniciar sesión."));
exit;
diff --git a/public_html/aulatek/__menu.php b/public_html/aulatek/__menu.php
index e294b1d..a5dd4fd 100755
--- a/public_html/aulatek/__menu.php
+++ b/public_html/aulatek/__menu.php
@@ -1,11 +1,10 @@
-
-
-
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/public_html/aulatek/_filefetch.php b/public_html/aulatek/_filefetch.php
index 644baeb..b73b20e 100755
--- a/public_html/aulatek/_filefetch.php
+++ b/public_html/aulatek/_filefetch.php
@@ -10,9 +10,10 @@ header("Access-Control-Allow-Origin: *");
$type = $_GET["type"] ?? "";
+$orgs_base_dir = basename(aulatek_orgs_base_path());
switch ($type) {
case "alumno_photo":
- $centro = safe_centro_id($_GET["centro"] ?? '');
+ $centro = safe_organization_id($_GET["organization"] ?? $_GET["organizacion"] ?? $_GET["org"] ?? $_GET["centro"] ?? '');
$aulario = safe_id_segment($_GET["aulario"] ?? '');
$alumno = safe_id_segment($_GET["alumno"] ?? '');
// Additional validation to prevent empty names
@@ -20,19 +21,19 @@ switch ($type) {
header("HTTP/1.1 403 Forbidden");
die("Invalid parameters");
}
- $relpath = "entreaulas/Centros/$centro/Aularios/$aulario/Alumnos/$alumno/photo.jpg";
+ $relpath = "entreaulas/$orgs_base_dir/$centro/Aularios/$aulario/Alumnos/$alumno/photo.jpg";
break;
case "panel_actividades":
- $centro = safe_centro_id($_GET["centro"] ?? '');
+ $centro = safe_organization_id($_GET["organization"] ?? $_GET["organizacion"] ?? $_GET["org"] ?? $_GET["centro"] ?? '');
$activity = safe_id_segment($_GET["activity"] ?? '');
if (empty($centro) || empty($activity)) {
header("HTTP/1.1 400 Bad Request");
die("Invalid parameters");
}
- $relpath = "entreaulas/Centros/$centro/Panel/Actividades/$activity/photo.jpg";
+ $relpath = "entreaulas/$orgs_base_dir/$centro/Panel/Actividades/$activity/photo.jpg";
break;
case "comedor_image":
- $centro = safe_centro_id($_GET["centro"] ?? '');
+ $centro = safe_organization_id($_GET["organization"] ?? $_GET["organizacion"] ?? $_GET["org"] ?? $_GET["centro"] ?? '');
$aulario = safe_id_segment($_GET["aulario"] ?? '');
$date = preg_replace('/[^0-9-]/', '', $_GET["date"] ?? '');
$file = safe_filename($_GET["file"] ?? '');
@@ -46,10 +47,10 @@ switch ($type) {
}
$ym = substr($date, 0, 7);
$day = substr($date, 8, 2);
- $relpath = "entreaulas/Centros/$centro/Aularios/$aulario/Comedor/$ym/$day/$file";
+ $relpath = "entreaulas/$orgs_base_dir/$centro/Aularios/$aulario/Comedor/$ym/$day/$file";
break;
case "proyecto_file":
- $centro = safe_centro_id($_GET["centro"] ?? '');
+ $centro = safe_organization_id($_GET["organization"] ?? $_GET["organizacion"] ?? $_GET["org"] ?? $_GET["centro"] ?? '');
$project = safe_id_segment($_GET["project"] ?? '');
$file = safe_filename($_GET["file"] ?? '');
if (empty($centro) || empty($project) || empty($file)) {
@@ -61,7 +62,7 @@ switch ($type) {
header("HTTP/1.1 400 Bad Request");
die("Invalid file name");
}
- $projects_base = "/DATA/entreaulas/Centros/$centro/Proyectos";
+ $projects_base = aulatek_orgs_base_path() . "/$centro/Proyectos";
$project_dir = null;
if (is_dir($projects_base)) {
$iterator = new RecursiveIteratorIterator(
diff --git a/public_html/aulatek/_incl/auth_redir.php b/public_html/aulatek/_incl/auth_redir.php
index d6924c2..9f9ef06 100755
--- a/public_html/aulatek/_incl/auth_redir.php
+++ b/public_html/aulatek/_incl/auth_redir.php
@@ -1,5 +1,5 @@
Foto actual:
-
@@ -351,7 +353,7 @@ switch ($_GET["action"] ?? '') {
-
@@ -379,7 +381,7 @@ switch ($_GET["action"] ?? '') {
- Volver al Aulario
+ Volver al Aulario
"Access denied", "code" => "FORBIDDEN"]));
}
-$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? "");
+$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []);
+$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ""));
if ($centro_id === "") {
http_response_code(400);
- die(json_encode(["error" => "Centro not found in session", "code" => "INVALID_SESSION"]));
+ die(json_encode(["error" => "Organizacion not found in session", "code" => "INVALID_SESSION"]));
}
-$action = $_GET["action"] ?? ($_POST["action"] ?? "");
+$action = $_GET["action"] ?? ($_POST["action"] ?? "");
$aulario_id = safe_id_segment($_GET["aulario"] ?? $_POST["aulario"] ?? "");
-// Validate aulario_id
if ($aulario_id === "") {
http_response_code(400);
die(json_encode(["error" => "aulario parameter is required", "code" => "MISSING_PARAM"]));
}
-// Verify that the user has access to this aulario
-$userAulas = $_SESSION["auth_data"]["entreaulas"]["aulas"] ?? [];
-$userAulas = array_values(array_filter(array_map('safe_id_segment', $userAulas)));
+$userAulas = array_values(array_filter(array_map('safe_id_segment', $tenant_data["aulas"] ?? [])));
if (!in_array($aulario_id, $userAulas, true)) {
http_response_code(403);
die(json_encode(["error" => "Access denied to this aulario", "code" => "FORBIDDEN"]));
}
-$aulario_path = safe_aulario_config_path($centro_id, $aulario_id);
-$aulario = ($aulario_path && file_exists($aulario_path)) ? json_decode(file_get_contents($aulario_path), true) : null;
+$aulario = db_get_aulario($centro_id, $aulario_id);
-// Handle shared comedor data
$source_aulario_id = $aulario_id;
$is_shared = false;
if ($aulario && !empty($aulario["shared_comedor_from"])) {
$shared_from = safe_id_segment($aulario["shared_comedor_from"]);
- $shared_aulario_path = safe_aulario_config_path($centro_id, $shared_from);
- if ($shared_aulario_path && file_exists($shared_aulario_path)) {
+ if (db_get_aulario($centro_id, $shared_from)) {
$source_aulario_id = $shared_from;
$is_shared = true;
}
}
-// Check edit permissions (must be sysadmin and not shared)
$canEdit = in_array("sysadmin:access", $_SESSION["auth_data"]["permissions"] ?? []) && !$is_shared;
-// Helper functions
-function get_menu_types($centro_id, $source_aulario_id) {
- $menuTypesPath = menu_types_path($centro_id, $source_aulario_id);
- $defaultMenuTypes = [
- ["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"],
- ["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"],
- ["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"],
- ];
+$defaultMenuTypes = [
+ ["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"],
+ ["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"],
+ ["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"],
+];
- if ($menuTypesPath === null) {
+function get_menu_types($centro_id, $source_aulario_id) {
+ global $defaultMenuTypes;
+ $types = db_get_comedor_menu_types($centro_id, $source_aulario_id);
+ if (empty($types)) {
+ db_set_comedor_menu_types($centro_id, $source_aulario_id, $defaultMenuTypes);
return $defaultMenuTypes;
}
-
- if (!file_exists($menuTypesPath)) {
- if (!is_dir(dirname($menuTypesPath))) {
- mkdir(dirname($menuTypesPath), 0777, true);
- }
- file_put_contents($menuTypesPath, json_encode($defaultMenuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
- return $defaultMenuTypes;
- }
-
- $menuTypes = json_decode(@file_get_contents($menuTypesPath), true);
- return (is_array($menuTypes) && count($menuTypes) > 0) ? $menuTypes : $defaultMenuTypes;
+ return $types;
}
function blank_menu() {
@@ -98,7 +67,7 @@ function blank_menu() {
"plates" => [
"primero" => ["name" => "", "pictogram" => ""],
"segundo" => ["name" => "", "pictogram" => ""],
- "postre" => ["name" => "", "pictogram" => ""],
+ "postre" => ["name" => "", "pictogram" => ""],
]
];
}
@@ -113,43 +82,25 @@ switch ($action) {
case "get_menu_types":
handle_get_menu_types();
break;
-
case "get_menu":
handle_get_menu();
break;
-
case "save_menu":
- if (!$canEdit) {
- http_response_code(403);
- die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"]));
- }
+ if (!$canEdit) { http_response_code(403); die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"])); }
handle_save_menu();
break;
-
case "add_menu_type":
- if (!$canEdit) {
- http_response_code(403);
- die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"]));
- }
+ if (!$canEdit) { http_response_code(403); die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"])); }
handle_add_menu_type();
break;
-
case "delete_menu_type":
- if (!$canEdit) {
- http_response_code(403);
- die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"]));
- }
+ if (!$canEdit) { http_response_code(403); die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"])); }
handle_delete_menu_type();
break;
-
case "rename_menu_type":
- if (!$canEdit) {
- http_response_code(403);
- die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"]));
- }
+ if (!$canEdit) { http_response_code(403); die(json_encode(["error" => "Insufficient permissions to edit", "code" => "FORBIDDEN"])); }
handle_rename_menu_type();
break;
-
default:
http_response_code(400);
die(json_encode(["error" => "Invalid action", "code" => "INVALID_ACTION"]));
@@ -157,298 +108,102 @@ switch ($action) {
function handle_get_menu_types() {
global $centro_id, $source_aulario_id;
-
- $menuTypes = get_menu_types($centro_id, $source_aulario_id);
- echo json_encode([
- "success" => true,
- "menu_types" => $menuTypes
- ]);
+ echo json_encode(["success" => true, "menu_types" => get_menu_types($centro_id, $source_aulario_id)]);
}
function handle_get_menu() {
global $centro_id, $source_aulario_id;
-
$date = $_GET["date"] ?? date("Y-m-d");
$menuTypeId = safe_id_segment($_GET["menu"] ?? "");
-
- // Validate date
$dateObj = DateTime::createFromFormat("Y-m-d", $date);
- if (!$dateObj) {
- http_response_code(400);
- die(json_encode(["error" => "Invalid date format", "code" => "INVALID_FORMAT"]));
- }
+ if (!$dateObj) { http_response_code(400); die(json_encode(["error" => "Invalid date format", "code" => "INVALID_FORMAT"])); }
$date = $dateObj->format("Y-m-d");
-
- // Get menu types
- $menuTypes = get_menu_types($centro_id, $source_aulario_id);
- $menuTypeIds = [];
- foreach ($menuTypes as $t) {
- if (!empty($t["id"])) {
- $menuTypeIds[] = $t["id"];
- }
- }
-
- if ($menuTypeId === "" || !in_array($menuTypeId, $menuTypeIds)) {
- $menuTypeId = $menuTypeIds[0] ?? "basal";
- }
-
- // Get menu data
- $ym = $dateObj->format("Y-m");
+ $menuTypes = get_menu_types($centro_id, $source_aulario_id);
+ $menuTypeIds = array_column($menuTypes, "id");
+ if ($menuTypeId === "" || !in_array($menuTypeId, $menuTypeIds)) { $menuTypeId = $menuTypeIds[0] ?? "basal"; }
+ $ym = $dateObj->format("Y-m");
$day = $dateObj->format("d");
- $baseDir = comedor_day_base_dir($centro_id, $source_aulario_id, $ym, $day);
- if ($baseDir === null) {
- http_response_code(400);
- die(json_encode(["error" => "Invalid path parameters", "code" => "INVALID_PATH"]));
- }
- $dataPath = "$baseDir/_datos.json";
-
- $menuData = [
- "date" => $date,
- "menus" => []
- ];
-
- if (file_exists($dataPath)) {
- $existing = json_decode(file_get_contents($dataPath), true);
- if (is_array($existing)) {
- $menuData = array_merge($menuData, $existing);
- }
- }
-
- if (!isset($menuData["menus"][$menuTypeId])) {
- $menuData["menus"][$menuTypeId] = blank_menu();
- }
-
- $menuForType = $menuData["menus"][$menuTypeId];
-
- echo json_encode([
- "success" => true,
- "date" => $date,
- "menu_type" => $menuTypeId,
- "menu_types" => $menuTypes,
- "menu" => $menuForType
- ]);
+ $menuData = ["date" => $date, "menus" => []];
+ $existing = db_get_comedor_entry($centro_id, $source_aulario_id, $ym, $day);
+ if (!empty($existing)) { $menuData = array_merge($menuData, $existing); }
+ if (!isset($menuData["menus"][$menuTypeId])) { $menuData["menus"][$menuTypeId] = blank_menu(); }
+ echo json_encode(["success" => true, "date" => $date, "menu_type" => $menuTypeId, "menu_types" => $menuTypes, "menu" => $menuData["menus"][$menuTypeId]]);
}
function handle_save_menu() {
global $centro_id, $source_aulario_id;
-
- // Parse JSON body
- $input = json_decode(file_get_contents("php://input"), true);
- if (!$input) {
- $input = $_POST;
- }
-
- $date = $input["date"] ?? date("Y-m-d");
+ $input = json_decode(file_get_contents("php://input"), true) ?: $_POST;
+ $date = $input["date"] ?? date("Y-m-d");
$menuTypeId = safe_id_segment($input["menu_type"] ?? "");
- $plates = $input["plates"] ?? [];
-
- // Validate date
+ $plates = $input["plates"] ?? [];
$dateObj = DateTime::createFromFormat("Y-m-d", $date);
- if (!$dateObj) {
- http_response_code(400);
- die(json_encode(["error" => "Invalid date format", "code" => "INVALID_FORMAT"]));
- }
+ if (!$dateObj) { http_response_code(400); die(json_encode(["error" => "Invalid date format", "code" => "INVALID_FORMAT"])); }
$date = $dateObj->format("Y-m-d");
-
- // Validate menu type
- $menuTypes = get_menu_types($centro_id, $source_aulario_id);
- $validMenuTypeIds = [];
- foreach ($menuTypes as $t) {
- if (!empty($t["id"])) {
- $validMenuTypeIds[] = $t["id"];
- }
- }
-
- if (!in_array($menuTypeId, $validMenuTypeIds)) {
- http_response_code(400);
- die(json_encode(["error" => "Invalid menu type", "code" => "INVALID_MENU_TYPE"]));
- }
-
- // Get existing menu data
- $ym = $dateObj->format("Y-m");
+ $menuTypes = get_menu_types($centro_id, $source_aulario_id);
+ $validMenuTypeIds = array_column($menuTypes, "id");
+ if (!in_array($menuTypeId, $validMenuTypeIds)) { http_response_code(400); die(json_encode(["error" => "Invalid menu type", "code" => "INVALID_MENU_TYPE"])); }
+ $ym = $dateObj->format("Y-m");
$day = $dateObj->format("d");
- $baseDir = comedor_day_base_dir($centro_id, $source_aulario_id, $ym, $day);
- if ($baseDir === null) {
- http_response_code(400);
- die(json_encode(["error" => "Invalid path parameters", "code" => "INVALID_PATH"]));
- }
- $dataPath = "$baseDir/_datos.json";
-
- $menuData = [
- "date" => $date,
- "menus" => []
- ];
-
- if (file_exists($dataPath)) {
- $existing = json_decode(file_get_contents($dataPath), true);
- if (is_array($existing)) {
- $menuData = array_merge($menuData, $existing);
+ $menuData = ["date" => $date, "menus" => []];
+ $existing = db_get_comedor_entry($centro_id, $source_aulario_id, $ym, $day);
+ if (!empty($existing)) { $menuData = array_merge($menuData, $existing); }
+ if (!isset($menuData["menus"][$menuTypeId])) { $menuData["menus"][$menuTypeId] = blank_menu(); }
+ foreach (["primero", "segundo", "postre"] as $plateKey) {
+ if (isset($plates[$plateKey]["name"])) {
+ $menuData["menus"][$menuTypeId]["plates"][$plateKey]["name"] = trim($plates[$plateKey]["name"]);
}
}
-
- if (!isset($menuData["menus"][$menuTypeId])) {
- $menuData["menus"][$menuTypeId] = blank_menu();
- }
-
- // Update plates
- $validPlates = ["primero", "segundo", "postre"];
- foreach ($validPlates as $plateKey) {
- if (isset($plates[$plateKey])) {
- if (isset($plates[$plateKey]["name"])) {
- $menuData["menus"][$menuTypeId]["plates"][$plateKey]["name"] = trim($plates[$plateKey]["name"]);
- }
- // Note: pictogram upload not supported via JSON API - use form-data instead
- }
- }
-
- // Save menu
- if (!is_dir($baseDir)) {
- mkdir($baseDir, 0777, true);
- }
- file_put_contents($dataPath, json_encode($menuData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
-
- echo json_encode([
- "success" => true,
- "date" => $date,
- "menu_type" => $menuTypeId,
- "menu" => $menuData["menus"][$menuTypeId]
- ]);
+ db_set_comedor_entry($centro_id, $source_aulario_id, $ym, $day, $menuData);
+ echo json_encode(["success" => true, "date" => $date, "menu_type" => $menuTypeId, "menu" => $menuData["menus"][$menuTypeId]]);
}
function handle_add_menu_type() {
global $centro_id, $source_aulario_id;
-
- $input = json_decode(file_get_contents("php://input"), true);
- if (!$input) {
- $input = $_POST;
- }
-
- $newId = safe_id_segment(strtolower(trim($input["id"] ?? "")));
+ $input = json_decode(file_get_contents("php://input"), true) ?: $_POST;
+ $newId = safe_id_segment(strtolower(trim($input["id"] ?? "")));
$newLabel = trim($input["label"] ?? "");
$newColor = trim($input["color"] ?? "#0d6efd");
-
- if ($newId === "" || $newLabel === "") {
- http_response_code(400);
- die(json_encode(["error" => "id and label are required", "code" => "MISSING_PARAM"]));
- }
-
- $menuTypesPath = menu_types_path($centro_id, $source_aulario_id);
- if ($menuTypesPath === null) {
- http_response_code(400);
- die(json_encode(["error" => "Invalid path parameters", "code" => "INVALID_PATH"]));
- }
+ if ($newId === "" || $newLabel === "") { http_response_code(400); die(json_encode(["error" => "id and label are required", "code" => "MISSING_PARAM"])); }
$menuTypes = get_menu_types($centro_id, $source_aulario_id);
-
- // Check if already exists
foreach ($menuTypes as $t) {
- if (($t["id"] ?? "") === $newId) {
- http_response_code(400);
- die(json_encode(["error" => "Menu type already exists", "code" => "DUPLICATE"]));
- }
+ if (($t["id"] ?? "") === $newId) { http_response_code(400); die(json_encode(["error" => "Menu type already exists", "code" => "DUPLICATE"])); }
}
-
$menuTypes[] = ["id" => $newId, "label" => $newLabel, "color" => $newColor];
- file_put_contents($menuTypesPath, json_encode($menuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
-
- echo json_encode([
- "success" => true,
- "menu_type" => ["id" => $newId, "label" => $newLabel, "color" => $newColor],
- "message" => "Menu type added successfully"
- ]);
+ db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
+ echo json_encode(["success" => true, "menu_type" => ["id" => $newId, "label" => $newLabel, "color" => $newColor], "message" => "Menu type added successfully"]);
}
function handle_delete_menu_type() {
global $centro_id, $source_aulario_id;
-
- $input = json_decode(file_get_contents("php://input"), true);
- if (!$input) {
- $input = $_POST;
- }
-
+ $input = json_decode(file_get_contents("php://input"), true) ?: $_POST;
$deleteId = safe_id_segment(trim($input["id"] ?? ""));
-
- if ($deleteId === "") {
- http_response_code(400);
- die(json_encode(["error" => "id is required", "code" => "MISSING_PARAM"]));
- }
-
- $menuTypesPath = menu_types_path($centro_id, $source_aulario_id);
- if ($menuTypesPath === null) {
- http_response_code(400);
- die(json_encode(["error" => "Invalid path parameters", "code" => "INVALID_PATH"]));
- }
- $menuTypes = get_menu_types($centro_id, $source_aulario_id);
-
- $deleted = false;
- $newMenuTypes = [];
- foreach ($menuTypes as $t) {
- if (($t["id"] ?? "") === $deleteId) {
- $deleted = true;
- } else {
- $newMenuTypes[] = $t;
- }
- }
-
- if (!$deleted) {
- http_response_code(404);
- die(json_encode(["error" => "Menu type not found", "code" => "NOT_FOUND"]));
- }
-
- file_put_contents($menuTypesPath, json_encode($newMenuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
-
- echo json_encode([
- "success" => true,
- "message" => "Menu type deleted successfully"
- ]);
+ if ($deleteId === "") { http_response_code(400); die(json_encode(["error" => "id is required", "code" => "MISSING_PARAM"])); }
+ $menuTypes = get_menu_types($centro_id, $source_aulario_id);
+ $newMenuTypes = array_values(array_filter($menuTypes, fn($t) => ($t["id"] ?? "") !== $deleteId));
+ if (count($newMenuTypes) === count($menuTypes)) { http_response_code(404); die(json_encode(["error" => "Menu type not found", "code" => "NOT_FOUND"])); }
+ db_set_comedor_menu_types($centro_id, $source_aulario_id, $newMenuTypes);
+ echo json_encode(["success" => true, "message" => "Menu type deleted successfully"]);
}
function handle_rename_menu_type() {
global $centro_id, $source_aulario_id;
-
- $input = json_decode(file_get_contents("php://input"), true);
- if (!$input) {
- $input = $_POST;
- }
-
+ $input = json_decode(file_get_contents("php://input"), true) ?: $_POST;
$renameId = safe_id_segment(trim($input["id"] ?? ""));
$newLabel = trim($input["label"] ?? "");
$newColor = trim($input["color"] ?? "");
-
- if ($renameId === "" || $newLabel === "") {
- http_response_code(400);
- die(json_encode(["error" => "id and label are required", "code" => "MISSING_PARAM"]));
- }
-
- $menuTypesPath = menu_types_path($centro_id, $source_aulario_id);
- if ($menuTypesPath === null) {
- http_response_code(400);
- die(json_encode(["error" => "Invalid path parameters", "code" => "INVALID_PATH"]));
- }
+ if ($renameId === "" || $newLabel === "") { http_response_code(400); die(json_encode(["error" => "id and label are required", "code" => "MISSING_PARAM"])); }
$menuTypes = get_menu_types($centro_id, $source_aulario_id);
-
$found = false;
foreach ($menuTypes as &$t) {
if (($t["id"] ?? "") === $renameId) {
$t["label"] = $newLabel;
- if ($newColor !== "") {
- $t["color"] = $newColor;
- }
+ if ($newColor !== "") { $t["color"] = $newColor; }
$found = true;
break;
}
}
unset($t);
-
- if (!$found) {
- http_response_code(404);
- die(json_encode(["error" => "Menu type not found", "code" => "NOT_FOUND"]));
- }
-
- file_put_contents($menuTypesPath, json_encode($menuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
-
- echo json_encode([
- "success" => true,
- "menu_type" => ["id" => $renameId, "label" => $newLabel, "color" => $newColor],
- "message" => "Menu type renamed successfully"
- ]);
+ if (!$found) { http_response_code(404); die(json_encode(["error" => "Menu type not found", "code" => "NOT_FOUND"])); }
+ db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
+ echo json_encode(["success" => true, "message" => "Menu type renamed successfully"]);
}
diff --git a/public_html/aulatek/aulario.php b/public_html/aulatek/aulario.php
index df80422..7ae8d6e 100644
--- a/public_html/aulatek/aulario.php
+++ b/public_html/aulatek/aulario.php
@@ -2,11 +2,12 @@
require_once "_incl/auth_redir.php";
require_once "_incl/pre-body.php";
require_once "../_incl/tools.security.php";
+require_once "../_incl/db.php";
$aulario_id = safe_id_segment($_GET["id"] ?? "");
-$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? "");
-$aulario_path = safe_aulario_config_path($centro_id, $aulario_id);
-$aulario = ($aulario_path && file_exists($aulario_path)) ? json_decode(file_get_contents($aulario_path), true) : null;
+$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []);
+$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ""));
+$aulario = db_get_aulario($centro_id, $aulario_id);
if (!$aulario || !is_array($aulario)) {
?>
@@ -26,13 +27,14 @@ if (!$aulario || !is_array($aulario)) {
-
+
Panel Diario
-
-
+
+
+
Gestión de Alumnos
@@ -46,13 +48,13 @@ if (!$aulario || !is_array($aulario)) {
-
+
Menú del Comedor
-
+
folder-multiple
diff --git a/public_html/aulatek/comedor.php b/public_html/aulatek/comedor.php
index 3b47bdd..72734ff 100644
--- a/public_html/aulatek/comedor.php
+++ b/public_html/aulatek/comedor.php
@@ -1,14 +1,17 @@
"basal", "label" => "Menú basal", "color" => "#0d6efd"],
+ ["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"],
["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"],
- ["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"],
+ ["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"],
];
-if (!file_exists($menuTypesPath)) {
- if (!is_dir(dirname($menuTypesPath))) {
- mkdir(dirname($menuTypesPath), 0777, true);
- }
- file_put_contents($menuTypesPath, json_encode($defaultMenuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
-}
-$menuTypes = json_decode(@file_get_contents($menuTypesPath), true);
+$menuTypes = db_get_comedor_menu_types($centro_id, $source_aulario_id);
if (!is_array($menuTypes) || count($menuTypes) === 0) {
$menuTypes = $defaultMenuTypes;
+ db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
}
$menuTypeIds = [];
@@ -71,10 +67,9 @@ if (!in_array($menuTypeId, $menuTypeIds, true)) {
$menuTypeId = $menuTypeIds[0] ?? "basal";
}
-$ym = $dateObj->format("Y-m");
+$ym = $dateObj->format("Y-m");
$day = $dateObj->format("d");
-$baseDir = "/DATA/entreaulas/Centros/$centro_id/Aularios/$source_aulario_id/Comedor/$ym/$day";
-$dataPath = "$baseDir/_datos.json";
+
function blank_menu()
{
@@ -91,11 +86,9 @@ $menuData = [
"date" => $date,
"menus" => []
];
-if (file_exists($dataPath)) {
- $existing = json_decode(file_get_contents($dataPath), true);
- if (is_array($existing)) {
- $menuData = array_merge($menuData, $existing);
- }
+$existing = db_get_comedor_entry($centro_id, $source_aulario_id, $ym, $day);
+if (is_array($existing) && !empty($existing)) {
+ $menuData = array_merge($menuData, $existing);
}
if (!isset($menuData["menus"][$menuTypeId])) {
$menuData["menus"][$menuTypeId] = blank_menu();
@@ -251,15 +244,12 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) {
if ($newId !== "" && $newLabel !== "") {
$exists = false;
foreach ($menuTypes as $t) {
- if (($t["id"] ?? "") === $newId) {
- $exists = true;
- break;
- }
+ if (($t["id"] ?? "") === $newId) { $exists = true; break; }
}
if (!$exists) {
$menuTypes[] = ["id" => $newId, "label" => $newLabel, "color" => $newColor];
- file_put_contents($menuTypesPath, json_encode($menuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
- header("Location: /entreaulas/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($newId));
+ db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
+ header("Location: /aulatek/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($newId));
exit;
}
}
@@ -268,21 +258,12 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) {
if ($action === "delete_type") {
$deleteId = safe_id_segment(trim($_POST["delete_type_id"] ?? ""));
if ($deleteId !== "") {
- $deleted = false;
- $newMenuTypes = [];
- foreach ($menuTypes as $t) {
- if (($t["id"] ?? "") === $deleteId) {
- $deleted = true;
- } else {
- $newMenuTypes[] = $t;
- }
- }
- if ($deleted) {
+ $newMenuTypes = array_values(array_filter($menuTypes, fn($t) => ($t["id"] ?? "") !== $deleteId));
+ if (count($newMenuTypes) !== count($menuTypes)) {
$menuTypes = $newMenuTypes;
- file_put_contents($menuTypesPath, json_encode($menuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
- // Redirect to the first available menu type or default
+ db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
$redirectMenuId = !empty($menuTypes) ? $menuTypes[0]["id"] : "basal";
- header("Location: /entreaulas/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($redirectMenuId));
+ header("Location: /aulatek/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($redirectMenuId));
exit;
}
}
@@ -296,16 +277,13 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) {
foreach ($menuTypes as &$t) {
if (($t["id"] ?? "") === $renameId) {
$t["label"] = $newLabel;
- if ($newColor !== "") {
- $t["color"] = $newColor;
- }
+ if ($newColor !== "") { $t["color"] = $newColor; }
break;
}
}
- // Clean up the reference to avoid accidental usage after the loop
unset($t);
- file_put_contents($menuTypesPath, json_encode($menuTypes, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
- header("Location: /entreaulas/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($renameId));
+ db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
+ header("Location: /aulatek/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($renameId));
exit;
}
}
@@ -316,23 +294,19 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) {
$menuData["menus"][$menuTypeId] = blank_menu();
}
+ // Pictogram images still stored on filesystem in Comedor dir
+ $baseDir = aulatek_orgs_base_path() . "/$centro_id/Aularios/$source_aulario_id/Comedor/$ym/$day";
$plates = ["primero", "segundo", "postre"];
foreach ($plates as $plate) {
$name = trim($_POST["name_" . $plate] ?? "");
$menuData["menus"][$menuTypeId]["plates"][$plate]["name"] = $name;
-
$pictUpload = handle_image_upload("pictogram_file_" . $plate, $menuTypeId . "_" . $plate . "_pict", $baseDir, $uploadErrors);
-
if ($pictUpload !== null) {
$menuData["menus"][$menuTypeId]["plates"][$plate]["pictogram"] = $pictUpload;
}
-
}
- if (!is_dir($baseDir)) {
- mkdir($baseDir, 0777, true);
- }
- file_put_contents($dataPath, json_encode($menuData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
+ db_set_comedor_entry($centro_id, $source_aulario_id, $ym, $day, $menuData);
$saveNotice = "Menú guardado correctamente.";
}
}
@@ -346,24 +320,23 @@ function image_src($value, $centro_id, $source_aulario_id, $date)
if (filter_var($value, FILTER_VALIDATE_URL)) {
return $value;
}
- return "/entreaulas/_filefetch.php?type=comedor_image¢ro=" . urlencode($centro_id) . "&aulario=" . urlencode($source_aulario_id) . "&date=" . urlencode($date) . "&file=" . urlencode($value);
+ return "/aulatek/_filefetch.php?type=comedor_image&org=" . urlencode($centro_id) . "&aulario=" . urlencode($source_aulario_id) . "&date=" . urlencode($date) . "&file=" . urlencode($value);
}
$prevDate = (clone $dateObj)->modify("-1 day")->format("Y-m-d");
$nextDate = (clone $dateObj)->modify("+1 day")->format("Y-m-d");
-$userAulas = $_SESSION["auth_data"]["entreaulas"]["aulas"] ?? [];
+$userAulas = $tenant_data["aulas"] ?? [];
$aulaOptions = [];
foreach ($userAulas as $aulaId) {
$aulaIdSafe = safe_id_segment($aulaId);
if ($aulaIdSafe === "") {
continue;
}
- $aulaPath = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aulaIdSafe.json";
- $aulaData = file_exists($aulaPath) ? json_decode(file_get_contents($aulaPath), true) : null;
+ $aulaData = db_get_aulario($centro_id, $aulaIdSafe);
$aulaOptions[] = [
- "id" => $aulaIdSafe,
- "name" => $aulaData["name"] ?? $aulaIdSafe
+ "id" => $aulaIdSafe,
+ "name" => $aulaData["name"] ?? $aulaIdSafe,
];
}
require_once "_incl/pre-body.php";
@@ -394,9 +367,9 @@ require_once "_incl/pre-body.php";
Aulario:
@@ -419,7 +392,7 @@ require_once "_incl/pre-body.php";
$isActive = ($type["id"] ?? "") === $menuTypeId;
$color = $type["color"] ?? "#0d6efd";
?>
-
"
+ "
class="btn btn-lg" style="background: = htmlspecialchars($color) ?>; color: white; border: 3px solid = $isActive ? "#000" : "transparent" ?>;">
= htmlspecialchars($type["label"] ?? $type["id"]) ?>
@@ -644,7 +617,7 @@ require_once "_incl/pre-body.php";
params.set("date", dateValue);
params.set("aulario", aularioValue);
params.set("menu", "= htmlspecialchars($menuTypeId) ?>");
- window.location.href = "/entreaulas/comedor.php?" + params.toString();
+ window.location.href = "/aulatek/comedor.php?" + params.toString();
}
if (datePicker) {
datePicker.addEventListener("change", goToSelection);
diff --git a/public_html/aulatek/diario.php b/public_html/aulatek/diario.php
index e0dbf9b..8d495f1 100644
--- a/public_html/aulatek/diario.php
+++ b/public_html/aulatek/diario.php
@@ -3,13 +3,15 @@ require_once "_incl/auth_redir.php";
require_once "../_incl/tools.security.php";
// Check if user has docente permission
-if (!in_array("entreaulas:docente", $_SESSION["auth_data"]["permissions"] ?? [])) {
+$permissions = $_SESSION["auth_data"]["permissions"] ?? [];
+if (!in_array("aulatek:docente", $permissions, true) && !in_array("entreaulas:docente", $permissions, true)) {
header("HTTP/1.1 403 Forbidden");
die("Acceso denegado");
}
$aulario_id = safe_id_segment($_GET["aulario"] ?? "");
-$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? "");
+$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []);
+$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ""));
$alumno = safe_id_segment($_GET["alumno"] ?? "");
if (empty($aulario_id) || empty($centro_id)) {
@@ -25,7 +27,7 @@ if (empty($aulario_id) || empty($centro_id)) {
}
// Validate paths with realpath
-$base_path = "/DATA/entreaulas/Centros";
+$base_path = aulatek_orgs_base_path();
$real_base = realpath($base_path);
if ($real_base === false) {
@@ -77,7 +79,7 @@ if (empty($alumno)) {
-
-
\ No newline at end of file
diff --git a/public_html/static/logo-aularios.png b/public_html/static/logo-aulatek.png
similarity index 100%
rename from public_html/static/logo-aularios.png
rename to public_html/static/logo-aulatek.png
diff --git a/public_html/sysadmin/__menu.php b/public_html/sysadmin/__menu.php
index e69de29..02e5844 100755
--- a/public_html/sysadmin/__menu.php
+++ b/public_html/sysadmin/__menu.php
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/public_html/sysadmin/aularios.php b/public_html/sysadmin/aularios.php
index ba15ace..76616b3 100644
--- a/public_html/sysadmin/aularios.php
+++ b/public_html/sysadmin/aularios.php
@@ -1,10 +1,11 @@
prepare("DELETE FROM aularios WHERE org_id = ? AND aulario_id = ?")
+ ->execute([$centro_id, $aulario_id]);
+ // Remove comedor, diario, panel data
+ db()->prepare("DELETE FROM comedor_menu_types WHERE org_id = ? AND aulario_id = ?")
+ ->execute([$centro_id, $aulario_id]);
+ db()->prepare("DELETE FROM comedor_entries WHERE org_id = ? AND aulario_id = ?")
+ ->execute([$centro_id, $aulario_id]);
+ db()->prepare("DELETE FROM diario_entries WHERE org_id = ? AND aulario_id = ?")
+ ->execute([$centro_id, $aulario_id]);
+ db()->prepare("DELETE FROM panel_alumno WHERE org_id = ? AND aulario_id = ?")
+ ->execute([$centro_id, $aulario_id]);
+ // Remove filesystem directory with student photos
+ $aulario_dir = aulatek_orgs_base_path() . "/$centro_id/Aularios/$aulario_id";
+ function rrmdir($dir)
+ {
if (is_dir($dir)) {
- $objects = scandir($dir);
- foreach ($objects as $object) {
- if ($object != "." && $object != "..") {
- $obj_path = $dir . "/" . $object;
- if (is_dir($obj_path)) {
- rrmdir($obj_path);
- } else {
- unlink($obj_path);
- }
+ foreach (scandir($dir) as $object) {
+ if ($object !== "." && $object !== "..") {
+ $p = "$dir/$object";
+ is_dir($p) ? rrmdir($p) : unlink($p);
}
}
rmdir($dir);
}
}
rrmdir($aulario_dir);
- // Remove aulario config file
- unlink($aulario_file);
header("Location: ?action=index");
exit();
break;
case "create":
- $user_data = $_SESSION["auth_data"];
- $centro_id = safe_path_segment(Sf($_POST["centro"] ?? ""));
- if (empty($centro_id) || !is_dir("/DATA/entreaulas/Centros/$centro_id")) {
+ $centro_id = safe_path_segment(Sf($_POST["centro"] ?? ($_POST["org"] ?? "")));
+ $aulario_id = strtolower(preg_replace("/[^a-zA-Z0-9_-]/", "_", Sf($_POST["name"] ?? "")));
+ if (empty($centro_id) || empty($aulario_id)) {
+ die("Datos incompletos.");
+ }
+ // Ensure centro exists in DB
+ $stmt = db()->prepare("SELECT id FROM organizaciones WHERE org_id = ?");
+ $stmt->execute([$centro_id]);
+ if (!$stmt->fetch()) {
die("Centro no válido.");
}
- $aulario_id = strtolower(preg_replace("/[^a-zA-Z0-9_-]/", "_", Sf($_POST["name"] ?? "")));
- $aulario_data = [
- "name" => Sf($_POST["name"] ?? ""),
- "icon" => Sf($_POST["icon"] ?? "/static/logo-entreaulas.png")
- ];
- // Make path recursive (mkdir -p equivalent)
- @mkdir("/DATA/entreaulas/Centros/$centro_id/Aularios/", 0777, true);
- @mkdir("/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id/Proyectos/", 0777, true);
- file_put_contents("/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id.json", json_encode($aulario_data));
- // Update user data
- $_SESSION["auth_data"]["entreaulas"]["aulas"][] = $aulario_id;
+ db()->prepare(
+ "INSERT OR IGNORE INTO aularios (org_id, aulario_id, name, icon) VALUES (?, ?, ?, ?)"
+ )->execute([
+ $centro_id, $aulario_id,
+ Sf($_POST["name"] ?? ""),
+ Sf($_POST["icon"] ?? "/static/logo-entreaulas.png"),
+ ]);
+ // Create Proyectos directory for project file storage
+ $proyectos_dir = aulatek_orgs_base_path() . "/$centro_id/Aularios/$aulario_id/Proyectos/";
+ if (!is_dir($proyectos_dir)) {
+ mkdir($proyectos_dir, 0755, true);
+ }
header("Location: ?action=index");
exit();
break;
case "save_edit":
$aulario_id = safe_path_segment(Sf($_POST["aulario_id"] ?? ""));
- $centro_id = safe_path_segment(Sf($_POST["centro_id"] ?? ""));
- $aulario_file = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id.json";
- if (!file_exists($aulario_file)) {
+ $centro_id = safe_path_segment(Sf($_POST["centro_id"] ?? ""));
+ if ($aulario_id === "" || $centro_id === "") {
+ die("Parámetros inválidos.");
+ }
+ // Fetch existing extra data
+ $existing = db_get_aulario($centro_id, $aulario_id);
+ if ($existing === null) {
die("Aulario no encontrado.");
}
- $aulario_data = json_decode(file_get_contents($aulario_file), true);
- $aulario_data["name"] = Sf($_POST["name"] ?? "");
- $aulario_data["icon"] = Sf($_POST["icon"] ?? "/static/logo-entreaulas.png");
-
- // Handle shared comedor configuration
- $share_comedor_from = safe_path_segment(Sf($_POST["share_comedor_from"] ?? ""));
-
- if (!empty($share_comedor_from) && $share_comedor_from !== "none") {
- $aulario_data["shared_comedor_from"] = $share_comedor_from;
- } else {
- unset($aulario_data["shared_comedor_from"]);
- }
-
- // Handle linked projects configuration
- $linked_projects = [];
- $linked_aularios = $_POST["linked_aulario"] ?? [];
- $linked_project_ids = $_POST["linked_project_id"] ?? [];
- $linked_permissions = $_POST["linked_permission"] ?? [];
-
- for ($i = 0; $i < count($linked_aularios); $i++) {
- $src_aul = safe_path_segment($linked_aularios[$i] ?? "");
- $proj_id = safe_path_segment($linked_project_ids[$i] ?? "");
- $perm = in_array(($linked_permissions[$i] ?? "read_only"), ["read_only", "request_edit", "full_edit"], true)
- ? ($linked_permissions[$i] ?? "read_only")
- : "read_only";
- if (!empty($src_aul) && !empty($proj_id)) {
- $linked_projects[] = [
- "source_aulario" => $src_aul,
- "project_id" => $proj_id,
- "permission" => $perm
- ];
+ // Build extra JSON preserving any existing extra fields
+ $extra_skip = ['name', 'icon'];
+ $extra = [];
+ foreach ($existing as $k => $v) {
+ if (!in_array($k, $extra_skip, true)) {
+ $extra[$k] = $v;
}
}
-
- if (count($linked_projects) > 0) {
- $aulario_data["linked_projects"] = $linked_projects;
- } else {
- unset($aulario_data["linked_projects"]);
+ // Update shared_comedor_from if posted
+ if (isset($_POST['shared_comedor_from'])) {
+ $extra['shared_comedor_from'] = Sf($_POST['shared_comedor_from']);
}
- @mkdir("/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id/Proyectos/", 0777, true);
- file_put_contents($aulario_file, json_encode($aulario_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
- header("Location: ?action=edit&aulario=" . urlencode($aulario_id) . "¢ro=" . urlencode($centro_id) . "&saved=1");
+ db()->prepare(
+ "UPDATE aularios SET name = ?, icon = ?, extra = ? WHERE org_id = ? AND aulario_id = ?"
+ )->execute([
+ Sf($_POST["name"] ?? ""),
+ Sf($_POST["icon"] ?? "/static/logo-entreaulas.png"),
+ json_encode($extra),
+ $centro_id,
+ $aulario_id,
+ ]);
+ header("Location: ?action=edit&aulario=" . urlencode($aulario_id) . "¢ro=" . urlencode($centro_id) . "&_result=" . urlencode("Cambios guardados."));
exit();
break;
}
-require_once "_incl/pre-body.php";
$view_action = $_GET["action"] ?? "index";
switch ($view_action) {
case "new":
- ?>
+ require_once "_incl/pre-body.php";
+ $centro_id = safe_path_segment(Sf($_GET["centro"] ?? ($_GET["org"] ?? "")));
+ $all_centros = db_get_centro_ids();
+?>
-
Nuevo Aulario
-
- Aquí puedes crear un nuevo aulario para el centro que administras.
-
+
Nuevo Aulario
-
$aul_name) {
- $proj_dir = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aul_id/Proyectos";
- if (is_dir($proj_dir)) {
- $projects = [];
- $files = glob("$proj_dir/*.json");
- foreach ($files as $file) {
- $proj_data = json_decode(file_get_contents($file), true);
- // Only include root projects (no parent)
- if ($proj_data && ($proj_data["parent_id"] ?? null) === null) {
- $projects[] = [
- "id" => $proj_data["id"] ?? basename($file, ".json"),
- "name" => $proj_data["name"] ?? "Sin nombre"
- ];
- }
- }
- if (count($projects) > 0) {
- $available_projects_by_aulario[$aul_id] = $projects;
- }
- }
- }
+ $other_aularios = db_get_aularios($centro_id);
?>
-
-
Cambios guardados correctamente.
-
-
Editar Aulario:
+
Aulario: = htmlspecialchars($aulario['name'] ?? $aulario_id) ?>
-
-
-
Gestión de Aularios
-
- Desde esta sección puedes administrar los aularios asociados al centro que estás administrando.
-
-
-
-
- Icono
- Nombre
-
- + Nuevo
-
-
-
-
- ';
- echo ' ';
- echo '' . htmlspecialchars($aula_data['name'] ?? 'Sin Nombre') . '' . $centro_id . ' ';
- echo 'Gestionar ';
- echo '';
- }
- ?>
-
-
+
Gestión de Aularios
+
+
+
= htmlspecialchars($c['centro_id']) ?>
+
+
+
+ Icono Nombre
+ + Nuevo
+
+
+
+ $adata): ?>
+
+
+ = htmlspecialchars($adata['name'] ?: $aid) ?>
+ Editar
+
+
+
+
+
-
\ No newline at end of file
diff --git a/public_html/sysadmin/centros.php b/public_html/sysadmin/centros.php
deleted file mode 100644
index 5cb06dd..0000000
--- a/public_html/sysadmin/centros.php
+++ /dev/null
@@ -1,293 +0,0 @@
-
-
-
-
Gestión de la Actividad:
-
- Desde esta sección puedes administrar la actividad seleccionada del panel del centro .
-
-
-
-
-
-
-
-
Nueva Actividad del Panel
-
- Aquí puedes crear una nueva actividad para el panel del centro .
-
-
-
-
-
-
-
-
Nuevo Centro
-
- Aquí puedes crear un nuevo centro para el sistema.
-
-
-
-
-
-
-
-
-
Gestión del Centro:
-
- Desde esta sección puedes administrar el centro seleccionado.
-
-
-
-
-
-
Aularios
-
-
-
- Icono
- Nombre
-
- + Nuevo
-
-
-
-
- ';
- echo ' ';
- echo '' . htmlspecialchars($aula_data['name'] ?? 'Sin Nombre') . ' ';
- echo 'Gestionar ';
- echo '';
- }
- ?>
-
-
-
-
-
-
-
Actividades del panel
-
-
-
- Foto
- Nombre
-
- + Nuevo
-
-
-
-
- ';
- echo ' ';
- echo '' . htmlspecialchars($activity_name) . ' ';
- echo 'Gestionar ';
- echo '';
- }
- ?>
-
-
-
-
-
-
-
-
-
Gestión de Centros
-
- Desde esta sección puedes administrar los centros asociados al sistema.
-
-
-
-
- Nombre
-
- + Nuevo
-
-
-
-
- ';
- echo '' . htmlspecialchars($centro_id) . ' ';
- echo 'Gestionar ';
- echo '';
- }
- ?>
-
-
-
-
-
\ No newline at end of file
diff --git a/public_html/sysadmin/index.php b/public_html/sysadmin/index.php
index 16a9260..ca1ae67 100644
--- a/public_html/sysadmin/index.php
+++ b/public_html/sysadmin/index.php
@@ -7,9 +7,9 @@ require_once "_incl/pre-body.php"; ?>
diff --git a/public_html/sysadmin/invitations.php b/public_html/sysadmin/invitations.php
index 2364f2e..50b1af6 100644
--- a/public_html/sysadmin/invitations.php
+++ b/public_html/sysadmin/invitations.php
@@ -1,100 +1,91 @@
true,
- "single_use" => $single_use
- ];
- file_put_contents("/DATA/Invitaciones_de_usuarios.json", json_encode($invitations, JSON_PRETTY_PRINT));
- header("Location: /sysadmin/invitations.php?_result=" . urlencode("Código $invitation_code creado correctamente."));
+ db_upsert_invitation($code, true, $single_use);
+ header("Location: /sysadmin/invitations.php?_result=" . urlencode("Código $code creado correctamente."));
exit;
break;
case "delete":
- // Handle deletion logic here
- $invitations = json_decode(file_get_contents("/DATA/Invitaciones_de_usuarios.json"), true) ?? [];
- $invitation_code = strtoupper($_POST['invitation_code'] ?? '');
- if (isset($invitations[$invitation_code])) {
- unset($invitations[$invitation_code]);
- file_put_contents("/DATA/Invitaciones_de_usuarios.json", json_encode($invitations, JSON_PRETTY_PRINT));
- }
- header("Location: /sysadmin/invitations.php?_result=" . urlencode("Codigo $invitation_code borrado"));
+ $code = strtoupper(trim($_POST['invitation_code'] ?? ''));
+ db_delete_invitation($code);
+ header("Location: /sysadmin/invitations.php?_result=" . urlencode("Código $code borrado."));
exit;
break;
}
require_once "_incl/pre-body.php";
-switch ($_GET['action']) {
+switch ($_GET['action'] ?? 'index') {
case "new":
- ?>
-
-
-
Nueva invitación de usuario
-
-
Restablecer Contraseña:
-
+
Restablecer Contraseña: = htmlspecialchars($username) ?>
Nueva Contraseña:
Mínimo 6 caracteres
-
Confirmar Contraseña:
-
-
-
+
Restablecer Contraseña
-
Cancelar
+
Cancelar
-
diff --git a/public_html/sysadmin/users.php b/public_html/sysadmin/users.php
index 5ab2696..ddb16b5 100644
--- a/public_html/sysadmin/users.php
+++ b/public_html/sysadmin/users.php
@@ -1,91 +1,108 @@
+
+ $_POST['display_name'] ?? '',
- 'email' => $_POST['email'] ?? '',
- 'permissions' => $permissions,
- 'entreaulas' => [
- 'centro' => safe_centro_id($_POST['centro'] ?? ''),
- 'role' => $_POST['role'] ?? '',
- 'aulas' => $aulas
- ]
- ];
- // Merge old and new data to preserve any other fields, like password hashes or custom metadata.
- $userdata = array_merge($userdata_old, $userdata_new);
- $user_dir = rtrim(USERS_DIR, '/');
- $user_file = get_user_file_path($username);
- if (!is_dir($user_dir) || !is_writable($user_dir)) {
- die("No se puede guardar el usuario: directorio de datos no disponible.");
- }
- $json_data = json_encode($userdata, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
- if ($json_data === false) {
- die("No se puede guardar el usuario: error al codificar los datos.");
- }
- $tmp_file = tempnam($user_dir, 'user_');
- if ($tmp_file === false) {
- die("No se puede guardar el usuario: no se pudo crear un archivo temporal.");
- }
- $bytes_written = file_put_contents($tmp_file, $json_data, LOCK_EX);
- if ($bytes_written === false) {
- @unlink($tmp_file);
- die("No se puede guardar el usuario: error al escribir en el disco.");
- }
- if (!rename($tmp_file, $user_file)) {
- @unlink($tmp_file);
- die("No se puede guardar el usuario: no se pudo finalizar la grabación del archivo.");
- }
- header("Location: ?action=edit&user=" . urlencode($username) . "&_result=" . urlencode("Cambios guardados correctamente a las ".date("H:i:s")." (hora servidor)."));
- exit;
- break;
+ $organization_input = $_POST['organization'] ?? [];
+ if (!is_array($organization_input)) {
+ $organization_input = [$organization_input];
+ }
+ $organizations = array_values(array_unique(array_filter(array_map('safe_organization_id', $organization_input))));
+
+ db_upsert_user([
+ 'username' => $username,
+ 'display_name' => $_POST['display_name'] ?? '',
+ 'email' => $_POST['email'] ?? '',
+ 'permissions' => $permissions,
+ 'orgs' => $organizations,
+ '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";
+ case 'add':
+ require_once "_incl/pre-body.php";
+ render_users_mobile_styles();
+ $all_organizations = db_get_organizations();
?>
-
+
Agregar Nuevo Usuario
@@ -104,70 +121,52 @@ switch ($_GET['action'] ?? '') {
Permisos: