From 0c362fd40bcc49204ddd2504397973bd984d8ee6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 6 Mar 2026 22:00:48 +0000
Subject: [PATCH] feat: SQLite DB with migrations replaces all JSON file
storage
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add db.php with PDO singleton, migration runner, and all helper functions
- Add migrations/001_initial_schema.sql (full schema)
- Add migrations/002_import_json.php (one-time JSON → DB importer)
- Add _incl/switch_tenant.php POST endpoint for tenant/centro switching
- Update tools.auth.php: DB-backed login, cookie auth, session reload, init_active_centro()
- Update all sysadmin pages (users, centros, aularios, invitations, reset_password) to use DB
- Update aulatek/index.php, aulario.php, supercafe.php, supercafe_edit.php to use DB
- Update aulatek/comedor.php and api/comedor.php to use DB
- Update aulatek/paneldiario.php: aulario config + comedor data from DB
- Update aulatek/proyectos.php: aulario config + sharing metadata from DB
- Update club/cal.php, index.php, edit_data.php, upload/upload.php to use DB
- Update account/index.php: rich profile, tenant list, aula list, session info, permissions
- Update pre-body.php account dropdown: shows active org + inline tenant switcher
- Update DATA_STRUCTURE.md to document DB approach and migration system
Co-authored-by: naielv <109038805+naielv@users.noreply.github.com>
---
DATA_STRUCTURE.md | 63 +-
Dockerfile | 2 +-
Dockerfile.dev | 2 +-
public_html/_incl/db.php | 589 ++++++++++++++++++
.../_incl/migrations/001_initial_schema.sql | 144 +++++
.../_incl/migrations/002_import_json.php | 255 ++++++++
public_html/_incl/pre-body.php | 28 +-
public_html/_incl/switch_tenant.php | 28 +
public_html/_incl/tools.auth.php | 146 ++---
public_html/_install.php | 25 +-
public_html/_login.php | 77 +--
public_html/account/index.php | 165 +++--
public_html/account/register.php | 39 +-
public_html/aulatek/api/comedor.php | 383 ++----------
public_html/aulatek/aulario.php | 6 +-
public_html/aulatek/comedor.php | 87 +--
public_html/aulatek/index.php | 25 +-
public_html/aulatek/paneldiario.php | 24 +-
public_html/aulatek/proyectos.php | 77 +--
public_html/aulatek/supercafe.php | 75 +--
public_html/aulatek/supercafe_edit.php | 122 +---
public_html/club/cal.php | 3 +-
public_html/club/edit_data.php | 19 +-
public_html/club/index.php | 3 +-
public_html/club/upload/upload.php | 5 +-
public_html/sysadmin/aularios.php | 468 ++++----------
public_html/sysadmin/centros.php | 186 +++---
public_html/sysadmin/invitations.php | 143 ++---
public_html/sysadmin/reset_password.php | 96 ++-
public_html/sysadmin/users.php | 411 +++++-------
30 files changed, 2050 insertions(+), 1646 deletions(-)
create mode 100644 public_html/_incl/db.php
create mode 100644 public_html/_incl/migrations/001_initial_schema.sql
create mode 100644 public_html/_incl/migrations/002_import_json.php
create mode 100644 public_html/_incl/switch_tenant.php
mode change 100755 => 100644 public_html/aulatek/index.php
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/public_html/_incl/db.php b/public_html/_incl/db.php
new file mode 100644
index 0000000..5980588
--- /dev/null
+++ b/public_html/_incl/db.php
@@ -0,0 +1,589 @@
+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.entreaulas.centro, .role, .aulas, .centros
+ */
+function db_build_auth_data(array $row): array
+{
+ $permissions = json_decode($row['permissions'] ?? '[]', true) ?: [];
+ $meta = json_decode($row['meta'] ?? '{}', true) ?: [];
+
+ // Fetch all centro assignments for this user
+ $stmt = db()->prepare(
+ 'SELECT centro_id, role, aulas
+ FROM user_centros
+ WHERE user_id = ?
+ ORDER BY centro_id'
+ );
+ $stmt->execute([$row['id']]);
+ $centro_rows = $stmt->fetchAll();
+
+ $ea = ['centro' => '', 'centros' => [], 'role' => '', 'aulas' => []];
+ if (!empty($centro_rows)) {
+ $first = $centro_rows[0];
+ $ea['centro'] = $first['centro_id']; // legacy compat
+ $ea['role'] = $first['role'];
+ $ea['aulas'] = json_decode($first['aulas'] ?? '[]', true) ?: [];
+ $ea['centros'] = array_column($centro_rows, 'centro_id');
+ $ea['centros_data'] = $centro_rows;
+ }
+
+ return array_merge($meta, [
+ 'display_name' => $row['display_name'],
+ 'email' => $row['email'],
+ 'password_hash' => $row['password_hash'],
+ 'permissions' => $permissions,
+ 'entreaulas' => $ea,
+ 'google_auth' => (bool) $row['google_auth'],
+ ]);
+}
+
+/**
+ * Create or update a user.
+ * $data keys: username, display_name, email, password_hash, permissions[],
+ * google_auth, entreaulas{centro,centros[],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'];
+ $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 centro assignments when entreaulas data is provided
+ if (array_key_exists('entreaulas', $data)) {
+ $ea = $data['entreaulas'] ?? [];
+ $pdo->prepare('DELETE FROM user_centros WHERE user_id = ?')->execute([$user_id]);
+
+ // Support both legacy single centro and new multi-centro
+ $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'] ?? []);
+
+ $ins_centro = $pdo->prepare('INSERT OR IGNORE INTO centros (centro_id) VALUES (?)');
+ $ins_uc = $pdo->prepare(
+ 'INSERT OR REPLACE INTO user_centros (user_id, centro_id, role, aulas) VALUES (?, ?, ?, ?)'
+ );
+ foreach ($centros as $cid) {
+ if ($cid === '') {
+ continue;
+ }
+ $ins_centro->execute([$cid]);
+ $ins_uc->execute([$user_id, $cid, $role, $aulas]);
+ }
+ }
+
+ return $user_id;
+}
+
+/** Delete a user and their centro assignments. */
+function db_delete_user(string $username): void
+{
+ db()->prepare('DELETE FROM users WHERE username = ?')->execute([strtolower($username)]);
+}
+
+// ── Centro helpers ────────────────────────────────────────────────────────────
+
+function db_get_centros(): array
+{
+ return db()->query('SELECT centro_id, name FROM centros ORDER BY centro_id')->fetchAll();
+}
+
+function db_get_centro_ids(): array
+{
+ return db()->query('SELECT centro_id FROM centros ORDER BY centro_id')->fetchAll(PDO::FETCH_COLUMN);
+}
+
+// ── 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 centro_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 centro_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 centro_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 (centro_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 centro_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 centro_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 (centro_id, order_ref, fecha, persona, comanda, notas, estado)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ ON CONFLICT(centro_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 centro_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 centro_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 centro_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 (centro_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 centro_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 (centro_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 centro_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 (centro_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 centro_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 (centro_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 centro IDs the authenticated user belongs to. */
+function get_user_centros(?array $auth_data = null): array
+{
+ $data = $auth_data ?? $_SESSION['auth_data'] ?? [];
+ $ea = $data['entreaulas'] ?? [];
+
+ if (!empty($ea['centros']) && is_array($ea['centros'])) {
+ return array_values($ea['centros']);
+ }
+ if (!empty($ea['centro'])) {
+ return [$ea['centro']];
+ }
+ return [];
+}
+
+/** Ensure $_SESSION['active_centro'] is set to a valid centro. */
+function init_active_centro(?array $auth_data = null): void
+{
+ $centros = get_user_centros($auth_data);
+ if (empty($centros)) {
+ $_SESSION['active_centro'] = null;
+ return;
+ }
+ if (!empty($_SESSION['active_centro']) && in_array($_SESSION['active_centro'], $centros, true)) {
+ return;
+ }
+ $_SESSION['active_centro'] = $centros[0];
+}
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..cbc5e9c
--- /dev/null
+++ b/public_html/_incl/migrations/002_import_json.php
@@ -0,0 +1,255 @@
+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
+ $row = $db->prepare("SELECT id FROM users WHERE username = ?")->execute([$username]);
+ $user_id = (int) $db->query("SELECT id FROM users WHERE username = " . $db->quote($username))->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/pre-body.php b/public_html/_incl/pre-body.php
index 9a39d47..0c4cc49 100755
--- a/public_html/_incl/pre-body.php
+++ b/public_html/_incl/pre-body.php
@@ -27,7 +27,11 @@ if (!empty($displayName)) {
$initials = mb_strtoupper($first . $last);
}
-?>
+// Tenant (centro) management
+$userCentros = get_user_centros($_SESSION["auth_data"] ?? []);
+$activeCentro = $_SESSION['active_centro'] ?? ($_SESSION["auth_data"]["entreaulas"]["centro"] ?? '');
+
+
@@ -526,6 +530,28 @@ if (!empty($displayName)) {
+
+
+
+ Organización activa
+
+
+ = htmlspecialchars($activeCentro ?: '–') ?>
+
+ 1): ?>
+
Cambiar organización:
+
+
+
+
+
+
Gestionar cuenta
diff --git a/public_html/_incl/switch_tenant.php b/public_html/_incl/switch_tenant.php
new file mode 100644
index 0000000..65e6891
--- /dev/null
+++ b/public_html/_incl/switch_tenant.php
@@ -0,0 +1,28 @@
+ 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_centro($_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)
-{
- return in_array($perm, $_SESSION["auth_data"]["permissions"] ?? []);
-}
-/**
- * Returns all centro/tenant IDs the authenticated user belongs to.
- * Supports both the legacy single-centro format (entreaulas.centro = "string")
- * and the new multi-tenant format (entreaulas.centros = ["a", "b"]).
- */
-function get_user_centros($auth_data = null)
+function user_has_permission(string $perm): bool
{
- $data = $auth_data ?? $_SESSION["auth_data"] ?? [];
- $ea = $data["entreaulas"] ?? [];
-
- if (!empty($ea["centros"]) && is_array($ea["centros"])) {
- return array_values($ea["centros"]);
- }
- if (!empty($ea["centro"])) {
- return [$ea["centro"]];
- }
- return [];
-}
-
-/**
- * Ensures $_SESSION['active_centro'] is set to a valid centro for the user.
- * Call after user data is loaded/reloaded.
- */
-function init_active_centro($auth_data = null)
-{
- $centros = get_user_centros($auth_data);
- if (empty($centros)) {
- $_SESSION['active_centro'] = null;
- return;
- }
- // Keep existing selection only if it is still valid
- if (!empty($_SESSION['active_centro']) && in_array($_SESSION['active_centro'], $centros, true)) {
- return;
- }
- $_SESSION['active_centro'] = $centros[0];
+ return in_array($perm, $_SESSION["auth_data"]["permissions"] ?? [], true);
}
diff --git a/public_html/_install.php b/public_html/_install.php
index 764e6ca..c9eb756 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', 'entreaulas: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..98d36e3 100755
--- a/public_html/_login.php
+++ b/public_html/_login.php
@@ -1,8 +1,9 @@
$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_centro($_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"] ?? "/");
@@ -161,20 +164,19 @@ if ($_GET["clear_session"] == "1") {
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_centro($_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 +184,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..aa9838b 100644
--- a/public_html/account/index.php
+++ b/public_html/account/index.php
@@ -1,44 +1,131 @@
-
-
-
¡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($activeCentro) ?>)
+
+
+
+
+
 ?>)
+
+
= htmlspecialchars($aula['name'] ?? $aula_id) ?>
+
Asignada
+
+
+
+
+
+
+
+
+
Permisos
+
+
+ = htmlspecialchars($p) ?>
+
+
+
+
+
+
+
+
Sesión Activa
+
ID Sesión= htmlspecialchars(substr(session_id(), 0, 12)) ?>…
+
Org. activa= htmlspecialchars($activeCentro ?: '–') ?>
+
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/api/comedor.php b/public_html/aulatek/api/comedor.php
index 320ca98..36aa8d9 100644
--- a/public_html/aulatek/api/comedor.php
+++ b/public_html/aulatek/api/comedor.php
@@ -2,24 +2,7 @@
header("Content-Type: application/json; charset=utf-8");
require_once "_incl/auth_redir.php";
require_once "../_incl/tools.security.php";
-
-function menu_types_path($centro_id, $aulario_id) {
- $centro = safe_centro_id($centro_id);
- $aulario = safe_id_segment($aulario_id);
- if ($centro === '' || $aulario === '') {
- return null;
- }
- return "/DATA/entreaulas/Centros/$centro/Aularios/$aulario/Comedor-MenuTypes.json";
-}
-
-function comedor_day_base_dir($centro_id, $aulario_id, $ym, $day) {
- $centro = safe_centro_id($centro_id);
- $aulario = safe_id_segment($aulario_id);
- if ($centro === '' || $aulario === '') {
- return null;
- }
- return "/DATA/entreaulas/Centros/$centro/Aularios/$aulario/Comedor/$ym/$day";
-}
+require_once "../../_incl/db.php";
// Check permissions
if (!in_array("entreaulas:docente", $_SESSION["auth_data"]["permissions"] ?? [])) {
@@ -33,64 +16,48 @@ if ($centro_id === "") {
die(json_encode(["error" => "Centro 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', $_SESSION["auth_data"]["entreaulas"]["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 +65,7 @@ function blank_menu() {
"plates" => [
"primero" => ["name" => "", "pictogram" => ""],
"segundo" => ["name" => "", "pictogram" => ""],
- "postre" => ["name" => "", "pictogram" => ""],
+ "postre" => ["name" => "", "pictogram" => ""],
]
];
}
@@ -113,43 +80,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 +106,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..9259f3f 100644
--- a/public_html/aulatek/aulario.php
+++ b/public_html/aulatek/aulario.php
@@ -2,11 +2,11 @@
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;
+$centro_id = safe_centro_id($_SESSION["auth_data"]["entreaulas"]["centro"] ?? "");
+$aulario = db_get_aulario($centro_id, $aulario_id);
if (!$aulario || !is_array($aulario)) {
?>
diff --git a/public_html/aulatek/comedor.php b/public_html/aulatek/comedor.php
index 3b47bdd..32e7d0d 100644
--- a/public_html/aulatek/comedor.php
+++ b/public_html/aulatek/comedor.php
@@ -1,6 +1,7 @@
"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 +65,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 +84,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,14 +242,11 @@ 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));
+ db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
header("Location: /entreaulas/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($newId));
exit;
}
@@ -268,19 +256,10 @@ 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));
exit;
@@ -296,15 +275,12 @@ 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));
+ db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
header("Location: /entreaulas/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($renameId));
exit;
}
@@ -316,23 +292,19 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) {
$menuData["menus"][$menuTypeId] = blank_menu();
}
+ // Pictogram images still stored on filesystem in Comedor dir
+ $baseDir = "/DATA/entreaulas/Centros/$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.";
}
}
@@ -359,11 +331,10 @@ foreach ($userAulas as $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";
diff --git a/public_html/aulatek/index.php b/public_html/aulatek/index.php
old mode 100755
new mode 100644
index ce0271a..09e12d7
--- a/public_html/aulatek/index.php
+++ b/public_html/aulatek/index.php
@@ -2,30 +2,29 @@
require_once "_incl/auth_redir.php";
require_once "_incl/pre-body.php";
require_once "../_incl/tools.security.php";
+require_once "../_incl/db.php";
?>
-
¡Hola, !
+ ¡Hola, !
Bienvenidx a la plataforma de gestión de aularios conectados. Desde aquí podrás administrar los aularios asociados a tu cuenta.
-
-
-
diff --git a/public_html/aulatek/paneldiario.php b/public_html/aulatek/paneldiario.php
index e11a5f3..056229a 100755
--- a/public_html/aulatek/paneldiario.php
+++ b/public_html/aulatek/paneldiario.php
@@ -1,6 +1,7 @@
format("Y-m-d");
- $menuTypesPath = ($centro_id !== '' && $source_aulario_id !== '') ? "/DATA/entreaulas/Centros/$centro_id/Aularios/$source_aulario_id/Comedor-MenuTypes.json" : "";
$defaultMenuTypes = [
["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"],
["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"],
["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"],
];
- $menuTypes = ($menuTypesPath !== '' && file_exists($menuTypesPath)) ? json_decode(@file_get_contents($menuTypesPath), true) : null;
+ $menuTypes = ($centro_id !== '' && $source_aulario_id !== '') ? db_get_comedor_menu_types($centro_id, $source_aulario_id) : [];
if (!is_array($menuTypes) || count($menuTypes) === 0) {
$menuTypes = $defaultMenuTypes;
}
@@ -822,17 +820,13 @@ switch ($view_action) {
$menuTypeId = $menuTypeIds[0] ?? "basal";
}
- $ym = $dateObj->format("Y-m");
+ $ym = $dateObj->format("Y-m");
$day = $dateObj->format("d");
- $dataPath = ($centro_id !== '' && $source_aulario_id !== '') ? "/DATA/entreaulas/Centros/$centro_id/Aularios/$source_aulario_id/Comedor/$ym/$day/_datos.json" : "";
- $menuData = [
- "date" => $date,
- "menus" => []
- ];
- if ($dataPath !== '' && file_exists($dataPath)) {
- $existing = json_decode(file_get_contents($dataPath), true);
- if (is_array($existing)) {
+ $menuData = ["date" => $date, "menus" => []];
+ if ($centro_id !== '' && $source_aulario_id !== '') {
+ $existing = db_get_comedor_entry($centro_id, $source_aulario_id, $ym, $day);
+ if (!empty($existing)) {
$menuData = array_merge($menuData, $existing);
}
}
diff --git a/public_html/aulatek/proyectos.php b/public_html/aulatek/proyectos.php
index 7ff347c..d68606b 100644
--- a/public_html/aulatek/proyectos.php
+++ b/public_html/aulatek/proyectos.php
@@ -1,6 +1,7 @@
$aulario_id,
- "project_id" => $project_id,
- "permission" => "request_edit"
+ "project_id" => $project_id,
+ "permission" => "request_edit",
];
$message = "Proyecto compartido correctamente.";
}
- file_put_contents($target_config_path, json_encode($target_config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
+ // Save back: build extra JSON excluding standard fields
+ $extra_skip = ['name', 'icon'];
+ $extra = [];
+ foreach ($target_config as $k => $v) {
+ if (!in_array($k, $extra_skip, true)) {
+ $extra[$k] = $v;
+ }
+ }
+ db()->prepare(
+ "UPDATE aularios SET extra = ? WHERE centro_id = ? AND aulario_id = ?"
+ )->execute([json_encode($extra, JSON_UNESCAPED_UNICODE), $centro_id, $target_aulario]);
}
}
}
@@ -1570,10 +1565,9 @@ $view = $current_project ? "project" : "list";
@@ -1636,27 +1630,14 @@ $view = $current_project ? "project" : "list";
$entry,
- "name" => $config["name"] ?? "Aulario Desconocido",
- "linked_projects" => $config["linked_projects"] ?? []
- ];
- }
- }
- }
+ foreach ($aularios_db as $aid => $adata) {
+ $aularios[] = [
+ "id" => $aid,
+ "name" => $adata["name"] ?? "Aulario Desconocido",
+ "linked_projects" => $adata["linked_projects"] ?? [],
+ ];
}
return $aularios;
}
@@ -1918,10 +1899,8 @@ $view = $current_project ? "project" : "list";
diff --git a/public_html/aulatek/supercafe.php b/public_html/aulatek/supercafe.php
index e1d0355..1b53a4a 100644
--- a/public_html/aulatek/supercafe.php
+++ b/public_html/aulatek/supercafe.php
@@ -1,6 +1,7 @@
$aulario_data) {
$aulario_name = $aulario_data['name'] ?? $aulario_id;
- $alumnos_path = "$aularios_path/$aulario_id/Alumnos";
+ $alumnos_path = "$aularios_dir/$aulario_id/Alumnos";
if (!is_dir($alumnos_path)) {
continue;
}
foreach (glob("$alumnos_path/*/", GLOB_ONLYDIR) ?: [] as $alumno_dir) {
$alumno_name = basename($alumno_dir);
- $key = $aulario_id . ':' . $alumno_name;
+ $key = $aulario_id . ':' . $alumno_name;
$personas[$key] = [
'Nombre' => $alumno_name,
'Region' => $aulario_name,
@@ -62,7 +58,6 @@ if ($centro_id === '') {
exit;
}
-define('SC_DATA_DIR', "/DATA/entreaulas/Centros/$centro_id/SuperCafe/Comandas");
define('SC_MAX_DEBTS', 3);
$estados_colores = [
@@ -81,20 +76,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $can_edit) {
$action = $_POST['action'] ?? '';
if ($action === 'change_status') {
- $order_id = safe_id($_POST['order_id'] ?? '');
+ $order_id = safe_id($_POST['order_id'] ?? '');
$new_status = $_POST['status'] ?? '';
if ($order_id !== '' && array_key_exists($new_status, $estados_colores)) {
- $order_file = SC_DATA_DIR . '/' . $order_id . '.json';
- if (is_readable($order_file)) {
- $data = json_decode(file_get_contents($order_file), true);
- if (is_array($data)) {
- $data['Estado'] = $new_status;
- file_put_contents(
- $order_file,
- json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE),
- LOCK_EX
- );
- }
+ $row = db_get_supercafe_order($centro_id, $order_id);
+ if ($row) {
+ db_upsert_supercafe_order(
+ $centro_id, $order_id,
+ $row['fecha'], $row['persona'], $row['comanda'], $row['notas'], $new_status
+ );
}
}
header('Location: /entreaulas/supercafe.php');
@@ -104,34 +94,30 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $can_edit) {
if ($action === 'delete') {
$order_id = safe_id($_POST['order_id'] ?? '');
if ($order_id !== '') {
- $order_file = SC_DATA_DIR . '/' . $order_id . '.json';
- if (is_file($order_file)) {
- unlink($order_file);
- }
+ db()->prepare('DELETE FROM supercafe_orders WHERE centro_id = ? AND order_ref = ?')
+ ->execute([$centro_id, $order_id]);
}
header('Location: /entreaulas/supercafe.php');
exit;
}
}
-// Load all orders
+// Load all orders from DB
+$db_orders = db_get_supercafe_orders($centro_id);
$orders = [];
-if (is_dir(SC_DATA_DIR)) {
- $files = glob(SC_DATA_DIR . '/*.json') ?: [];
- foreach ($files as $file) {
- $data = json_decode(file_get_contents($file), true);
- if (!is_array($data)) {
- continue;
- }
- $data['_id'] = basename($file, '.json');
- $orders[] = $data;
- }
+foreach ($db_orders as $row) {
+ $orders[] = [
+ '_id' => $row['order_ref'],
+ 'Fecha' => $row['fecha'],
+ 'Persona'=> $row['persona'],
+ 'Comanda'=> $row['comanda'],
+ 'Notas' => $row['notas'],
+ 'Estado' => $row['estado'],
+ ];
}
// Sort newest first (by Fecha desc)
-usort($orders, function ($a, $b) {
- return strcmp($b['Fecha'] ?? '', $a['Fecha'] ?? '');
-});
+usort($orders, fn($a, $b) => strcmp($b['Fecha'] ?? '', $a['Fecha'] ?? ''));
$orders_active = array_filter($orders, fn($o) => ($o['Estado'] ?? '') !== 'Deuda');
$orders_deuda = array_filter($orders, fn($o) => ($o['Estado'] ?? '') === 'Deuda');
@@ -139,6 +125,7 @@ $orders_deuda = array_filter($orders, fn($o) => ($o['Estado'] ?? '') === 'Deuda
require_once "_incl/pre-body.php";
?>
+
SuperCafe – Cafetería
diff --git a/public_html/aulatek/supercafe_edit.php b/public_html/aulatek/supercafe_edit.php
index ded87ea..2e187b7 100644
--- a/public_html/aulatek/supercafe_edit.php
+++ b/public_html/aulatek/supercafe_edit.php
@@ -1,6 +1,7 @@
$aulario_data) {
$aulario_name = $aulario_data['name'] ?? $aulario_id;
- $alumnos_path = "$aularios_path/$aulario_id/Alumnos";
+ $alumnos_path = "$aularios_dir/$aulario_id/Alumnos";
if (!is_dir($alumnos_path)) {
continue;
}
$alumno_dirs = glob("$alumnos_path/*/", GLOB_ONLYDIR) ?: [];
- usort($alumno_dirs, function ($a, $b) {
- return strcasecmp(basename($a), basename($b));
- });
+ usort($alumno_dirs, fn($a, $b) => strcasecmp(basename($a), basename($b)));
foreach ($alumno_dirs as $alumno_dir) {
$alumno_name = basename($alumno_dir);
- // Key uses ':' as separator; safe_id_segment chars [A-Za-z0-9_-] exclude ':'
- $key = $aulario_id . ':' . $alumno_name;
+ $key = $aulario_id . ':' . $alumno_name;
$personas[$key] = [
'Nombre' => $alumno_name,
'Region' => $aulario_name,
@@ -62,43 +51,14 @@ function sc_load_personas_from_alumnos($centro_id)
return $personas;
}
-function sc_load_menu($sc_base)
-{
- $path = "$sc_base/Menu.json";
- if (!file_exists($path)) {
- return [];
- }
- $data = json_decode(file_get_contents($path), true);
- return is_array($data) ? $data : [];
-}
-
-function sc_count_debts($persona_key)
-{
- if (!is_dir(SC_DATA_DIR)) {
- return 0;
- }
- $count = 0;
- foreach (glob(SC_DATA_DIR . '/*.json') ?: [] as $file) {
- $data = json_decode(file_get_contents($file), true);
- if (is_array($data)
- && ($data['Persona'] ?? '') === $persona_key
- && ($data['Estado'] ?? '') === 'Deuda') {
- $count++;
- }
- }
- return $count;
-}
-
// Determine if creating or editing
$order_id = safe_id($_GET['id'] ?? '');
$is_new = $order_id === '';
if ($is_new) {
- $raw_id = uniqid('sc', true);
- $order_id = preg_replace('/[^a-zA-Z0-9_-]/', '', $raw_id);
+ $order_id = db_next_supercafe_ref($centro_id);
}
-$order_file = SC_DATA_DIR . '/' . $order_id . '.json';
-
+// Load existing order from DB (or defaults)
$order_data = [
'Fecha' => date('Y-m-d'),
'Persona' => '',
@@ -106,15 +66,21 @@ $order_data = [
'Notas' => '',
'Estado' => 'Pedido',
];
-if (!$is_new && is_readable($order_file)) {
- $existing = json_decode(file_get_contents($order_file), true);
- if (is_array($existing)) {
- $order_data = array_merge($order_data, $existing);
+if (!$is_new) {
+ $existing = db_get_supercafe_order($centro_id, $order_id);
+ if ($existing) {
+ $order_data = [
+ 'Fecha' => $existing['fecha'],
+ 'Persona' => $existing['persona'],
+ 'Comanda' => $existing['comanda'],
+ 'Notas' => $existing['notas'],
+ 'Estado' => $existing['estado'],
+ ];
}
}
$personas = sc_load_personas_from_alumnos($centro_id);
-$menu = sc_load_menu($sc_base);
+$menu = db_get_supercafe_menu($centro_id);
// Group personas by aulario for the optgroup picker
$personas_by_aulario = [];
@@ -133,11 +99,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$estado = 'Pedido';
}
- // Validar persona
if ($persona_key === '' || (!empty($personas) && !array_key_exists($persona_key, $personas))) {
$error = '¡Hay que elegir una persona válida!';
} else {
- // Construir comanda desde los campos de categoría visual
$comanda_parts = [];
if (!empty($menu)) {
foreach ($menu as $category => $items) {
@@ -152,48 +116,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$comanda_parts[] = $manual;
}
}
- $comanda_str = implode(', ', $comanda_parts);
-
- // Comprobar deudas
- $prev_persona = $order_data['Persona'] ?? '';
+ $comanda_str = implode(', ', $comanda_parts);
+ $prev_persona = $order_data['Persona'];
if ($is_new || $prev_persona !== $persona_key) {
- $debt_count = sc_count_debts($persona_key);
+ $debt_count = db_supercafe_count_debts($centro_id, $persona_key);
if ($debt_count >= SC_MAX_DEBTS) {
$error = 'Esta persona tiene ' . $debt_count . ' comandas en deuda. No se puede realizar el pedido.';
}
}
-
if ($error === '') {
- $new_data = [
- 'Fecha' => date('Y-m-d'),
- 'Persona' => $persona_key,
- 'Comanda' => $comanda_str,
- 'Notas' => $notas,
- 'Estado' => $is_new ? 'Pedido' : $estado,
- ];
-
- if (!is_dir(SC_DATA_DIR)) {
- mkdir(SC_DATA_DIR, 0755, true);
- }
-
- $tmp = SC_DATA_DIR . '/.' . $order_id . '.tmp';
- $bytes = file_put_contents(
- $tmp,
- json_encode($new_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE),
- LOCK_EX
+ db_upsert_supercafe_order(
+ $centro_id, $order_id,
+ date('Y-m-d'), $persona_key, $comanda_str, $notas,
+ $is_new ? 'Pedido' : $estado
);
- if ($bytes === false || !rename($tmp, $order_file)) {
- @unlink($tmp);
- $error = 'Error al guardar la comanda.';
- } else {
- header('Location: /entreaulas/supercafe.php');
- exit;
- }
+ header('Location: /entreaulas/supercafe.php');
+ exit;
}
}
}
require_once "_incl/pre-body.php";
+
?>
Comanda = htmlspecialchars($order_id) ?>
diff --git a/public_html/club/cal.php b/public_html/club/cal.php
index 97ba209..d67ebb4 100755
--- a/public_html/club/cal.php
+++ b/public_html/club/cal.php
@@ -1,10 +1,11 @@
$_POST["title"],
- "note" => $_POST["note"],
- "mapa" => [
- "url" => $_POST["mapa_url"]
- ]
- ];
+ "title" => $_POST["title"],
+ "note" => $_POST["note"],
+ "mapa" => ["url" => $_POST["mapa_url"]],
+ ];
$file = $_POST["date"];
- $val = file_put_contents("/DATA/club/IMG/$file/data.json", json_encode($data, JSON_UNESCAPED_SLASHES));
+ db_set_club_event($file, $data);
header("Location: /club/");
die();
}
diff --git a/public_html/club/index.php b/public_html/club/index.php
index 167fd72..0dc861c 100755
--- a/public_html/club/index.php
+++ b/public_html/club/index.php
@@ -1,5 +1,6 @@
-
diff --git a/public_html/club/upload/upload.php b/public_html/club/upload/upload.php
index 920456a..0811646 100755
--- a/public_html/club/upload/upload.php
+++ b/public_html/club/upload/upload.php
@@ -1,7 +1,8 @@
prepare("DELETE FROM aularios WHERE centro_id = ? AND aulario_id = ?")
+ ->execute([$centro_id, $aulario_id]);
+ // Remove comedor, diario, panel data
+ db()->prepare("DELETE FROM comedor_menu_types WHERE centro_id = ? AND aulario_id = ?")
+ ->execute([$centro_id, $aulario_id]);
+ db()->prepare("DELETE FROM comedor_entries WHERE centro_id = ? AND aulario_id = ?")
+ ->execute([$centro_id, $aulario_id]);
+ db()->prepare("DELETE FROM diario_entries WHERE centro_id = ? AND aulario_id = ?")
+ ->execute([$centro_id, $aulario_id]);
+ db()->prepare("DELETE FROM panel_alumno WHERE centro_id = ? AND aulario_id = ?")
+ ->execute([$centro_id, $aulario_id]);
+ // Remove filesystem directory with student photos
$aulario_dir = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id";
- function rrmdir($dir) {
+ 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"] ?? ""));
+ $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 centros WHERE centro_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 (centro_id, aulario_id, name, icon) VALUES (?, ?, ?, ?)"
+ )->execute([
+ $centro_id, $aulario_id,
+ Sf($_POST["name"] ?? ""),
+ Sf($_POST["icon"] ?? "/static/logo-entreaulas.png"),
+ ]);
+ // Create Alumnos directory for photo-based features
+ @mkdir("/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id/Proyectos/", 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 centro_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"] ?? ""));
+ $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
index 5cb06dd..2aa6be5 100644
--- a/public_html/sysadmin/centros.php
+++ b/public_html/sysadmin/centros.php
@@ -1,10 +1,11 @@
prepare("SELECT id FROM centros WHERE centro_id = ?");
+ $existing->execute([$centro_id]);
+ if ($existing->fetch()) {
die("El centro ya existe.");
}
- mkdir($centro_path, 0777, true);
+ // Create DB record
+ db()->prepare("INSERT INTO centros (centro_id) VALUES (?)")->execute([$centro_id]);
+ // Keep filesystem directory for activity photos (Panel/Actividades)
+ $centro_path = "/DATA/entreaulas/Centros/$centro_id";
+ if (!is_dir($centro_path)) {
+ mkdir($centro_path, 0755, true);
+ }
header("Location: ?action=index");
exit();
break;
case "create_activity":
ini_set('memory_limit', '512M');
- ini_set("display_errors", 1);
ini_set('upload_max_filesize', '256M');
ini_set('post_max_size', '256M');
$centro_id = safe_path_segment(Sf($_GET['centro'] ?? ''));
- $centro_path = "/DATA/entreaulas/Centros/$centro_id";
- if (!is_dir($centro_path)) {
+ // Validate centro exists in DB
+ $stmt = db()->prepare("SELECT id FROM centros WHERE centro_id = ?");
+ $stmt->execute([$centro_id]);
+ if (!$stmt->fetch()) {
die("Centro no válido.");
}
- $activity_name = safe_path_segment(Sf($_POST["name"] ?? ''));
+ $activity_name = safe_path_segment(Sf($_POST["name"] ?? ''));
if (empty($activity_name)) {
die("Nombre de la actividad no proporcionado.");
}
@@ -47,22 +57,20 @@ switch ($form_action) {
if ($activity_photo === null || $activity_photo["error"] !== UPLOAD_ERR_OK) {
die("Error al subir la foto.");
}
- $activity_path = "$centro_path/Panel/Actividades/$activity_name";
+ $activity_path = "/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/$activity_name";
if (is_dir($activity_path)) {
die("La actividad ya existe.");
}
- mkdir($activity_path, 0777, true);
- $photo_path = "$activity_path/photo.jpg";
- move_uploaded_file($activity_photo["tmp_name"], $photo_path);
+ mkdir($activity_path, 0755, true);
+ move_uploaded_file($activity_photo["tmp_name"], "$activity_path/photo.jpg");
header("Location: ?action=edit¢ro=" . urlencode($centro_id));
exit();
break;
case "edit_activity":
ini_set('memory_limit', '512M');
- ini_set("display_errors", 1);
ini_set('upload_max_filesize', '256M');
ini_set('post_max_size', '256M');
- $centro_id = safe_path_segment(Sf($_GET['centro'] ?? ''));
+ $centro_id = safe_path_segment(Sf($_GET['centro'] ?? ''));
$activity_name = safe_path_segment(Sf($_GET['activity'] ?? ''));
$activity_path = "/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/$activity_name";
if (!is_dir($activity_path)) {
@@ -70,27 +78,26 @@ switch ($form_action) {
}
$activity_photo = $_FILES["file"] ?? null;
if ($activity_photo !== null && $activity_photo["error"] === UPLOAD_ERR_OK) {
- $photo_path = "$activity_path/photo.jpg";
- move_uploaded_file($activity_photo["tmp_name"], $photo_path);
+ move_uploaded_file($activity_photo["tmp_name"], "$activity_path/photo.jpg");
}
- if (safe_path_segment(Sf($_POST['nombre'] ?? '')) != $activity_name) {
- $new_activity_name = safe_path_segment(Sf($_POST['nombre'] ?? ''));
- $new_activity_path = "/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/$new_activity_name";
- if (is_dir($new_activity_path)) {
+ $new_name = safe_path_segment(Sf($_POST['nombre'] ?? ''));
+ if ($new_name !== $activity_name && $new_name !== '') {
+ $new_path = "/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/$new_name";
+ if (is_dir($new_path)) {
die("Ya existe una actividad con ese nombre.");
}
- rename($activity_path, $new_activity_path);
+ rename($activity_path, $new_path);
}
- header("Location: ?action=edit¢ro=" . urlencode($centro_id));;
+ header("Location: ?action=edit¢ro=" . urlencode($centro_id));
exit();
break;
}
-require_once "_incl/pre-body.php";
+require_once "_incl/pre-body.php";
$view_action = $_GET["action"] ?? "index";
switch ($view_action) {
case "edit_activity":
- $centro_id = safe_path_segment(Sf($_GET['centro'] ?? ''));
+ $centro_id = safe_path_segment(Sf($_GET['centro'] ?? ''));
$activity_name = safe_path_segment(Sf($_GET['activity'] ?? ''));
$activity_path = "/DATA/entreaulas/Centros/$centro_id/Panel/Actividades/$activity_name";
if (!is_dir($activity_path)) {
@@ -99,22 +106,20 @@ switch ($view_action) {
?>
-
Gestión de la Actividad:
-
- Desde esta sección puedes administrar la actividad seleccionada del panel del centro .
-
-