# Cambios en la Base de Datos — Aula Matinal

**Fecha:** 2026-04-30  
**Archivos modificados:** `aula_matinal_bd.sql`, `mTarifas.php`, `mFechaCurso.php`, `mRemesas.php`, `user/mControlAsistencia.php`

---

## Resumen de cambios

| # | Tabla | Cambio | Prioridad |
|---|-------|--------|-----------|
| 1 | `datos_aplicacion` | Añadida clave primaria `id`, restricción singleton, datos fusionados | 🔴 Crítico |
| 2 | `asistencia` | UNIQUE en `(idAlumno, fecha)` para evitar asistencias duplicadas | 🔴 Crítico |
| 3 | `asistencia` | `reciboEmitido bit(1)` → `tinyint(1)` para compatibilidad PHP | 🟠 Alto |
| 4 | `asistencia` | Índice en `fecha` para mejorar rendimiento | 🟠 Alto |
| 5 | `inscripciones` | `completada bit(1)` → `tinyint(1)` para compatibilidad PHP | 🟠 Alto |
| 6 | `dias_no_lectivos` | UNIQUE en `fecha` para evitar días no lectivos duplicados | 🟠 Alto |

---

## Detalle de cada cambio

---

### 1. `datos_aplicacion` — Clave primaria y fusión de datos (🔴 Crítico)

**Problema:**  
La tabla no tenía clave primaria. El código de `mTarifas.php` y `mFechaCurso.php` hacía `INSERT` en dos operaciones independientes, creando dos filas con los datos partidos:

```
fila 1: (NULL,         NULL,         11, 5.00, 50.00)  ← solo tarifas
fila 2: ('2025-09-12', '2026-06-22', NULL, NULL, NULL) ← solo fechas
```

Esto es un bug activo: dependiendo del orden en que MySQL devuelva las filas, el código puede leer valores `NULL` para las tarifas o las fechas del curso.

**Solución en BD:**  
- Añadida columna `id tinyint unsigned NOT NULL DEFAULT 1` como clave primaria
- Añadida restricción `CHECK (id = 1)` para garantizar que solo existe una fila
- Las dos filas del dump se han fusionado en una sola con todos los datos

**Migración para base de datos existente:**
```sql
-- 1. Añadir la columna id
ALTER TABLE datos_aplicacion
  ADD COLUMN id tinyint unsigned NOT NULL DEFAULT 1 FIRST,
  ADD PRIMARY KEY (id),
  ADD CONSTRAINT chk_singleton CHECK (id = 1);

-- 2. Fusionar las dos filas en una (ejecutar según los valores reales de tu BD)
-- Primero, averigua qué valores tiene cada fila:
SELECT * FROM datos_aplicacion;

-- Luego, borra las filas y recrea la única correcta con todos los datos:
DELETE FROM datos_aplicacion;
INSERT INTO datos_aplicacion (id, inicioCurso, finCurso, numDias, precioDia, precioMes)
VALUES (1, '2025-09-12', '2026-06-22', 11, 5.00, 50.00);
-- ↑ Ajusta estos valores a los reales de tu base de datos
```

**Cambios en PHP:**
- `mTarifas.php`: `listarTarifas()` simplificado a `WHERE id = 1`. `insertarTarifas()` reemplazado por UPSERT (`INSERT ... ON DUPLICATE KEY UPDATE`), eliminando la lógica de SELECT + INSERT/UPDATE que era la causa del bug.
- `mFechaCurso.php`: mismo patrón.
- `mRemesas.php`: `obtenerTarifas()` simplificado a `WHERE id = 1`.

---

### 2. `asistencia` — UNIQUE `(idAlumno, fecha)` (🔴 Crítico)

**Problema:**  
Sin restricción única, el mismo alumno podía tener dos registros de asistencia para el mismo día. Si el monitor marcaba dos veces, se generaban dos filas y el cobro se duplicaba en la remesa del mes.

**Solución en BD:**
```sql
UNIQUE KEY uq_alumno_fecha (idAlumno, fecha)
```

**Migración para base de datos existente:**
```sql
-- Verificar que no hay duplicados antes de añadir el UNIQUE:
SELECT idAlumno, fecha, COUNT(*) AS total
FROM asistencia
GROUP BY idAlumno, fecha
HAVING total > 1;
-- Si esta query devuelve filas, resolver los duplicados antes de continuar.

-- Añadir el índice único:
ALTER TABLE asistencia
  ADD UNIQUE KEY uq_alumno_fecha (idAlumno, fecha);
```

**Cambios en PHP:**
- `mControlAsistencia.php`: los dos `INSERT INTO asistencia` cambiados a `INSERT IGNORE INTO asistencia`. Así, si el monitor registra dos veces la asistencia del mismo alumno, la segunda llamada falla silenciosamente sin error, en vez de crear un duplicado o lanzar una excepción de clave duplicada.

---

### 3 y 5. `bit(1)` → `tinyint(1)` en `reciboEmitido` y `completada` (🟠 Alto)

**Problema:**  
MySQL almacena `bit(1)` como datos binarios. Cuando PHP lee un campo `bit(1)` vía `mysqli`, recibe el string `"\x01"` o `"\x00"`, no los enteros `1` o `0`. Esto puede hacer que comparaciones PHP como `$row['completada'] == 1` fallen silenciosamente.

El estándar en MySQL para columnas booleanas es `tinyint(1)`, que PHP lee correctamente como `1` o `0`.

**Solución en BD:**
```sql
ALTER TABLE asistencia
  MODIFY COLUMN reciboEmitido tinyint(1) NOT NULL DEFAULT 0;

ALTER TABLE inscripciones
  MODIFY COLUMN completada tinyint(1) NOT NULL DEFAULT 0;
```

En el dump SQL, los literales `b'0'` y `b'1'` se han sustituido por `0` y `1`.

**Cambios en PHP:**  
No se requieren cambios adicionales en PHP para este punto; las queries SQL que comparan `reciboEmitido = 0` o `completada = 1` funcionan igual con `tinyint(1)`.

---

### 4. `asistencia` — Índice en `fecha` (🟠 Alto)

**Problema:**  
La app consulta la asistencia frecuentemente filtrando por fecha (asistencia de hoy, resumen mensual). Solo existía el índice en `idAlumno`. Sin índice en `fecha`, MySQL hace un full scan de la tabla en estas queries.

**Solución en BD:**
```sql
ALTER TABLE asistencia
  ADD KEY idx_fecha (fecha);
```

El índice compuesto `UNIQUE KEY uq_alumno_fecha (idAlumno, fecha)` ya cubre búsquedas que filtran solo por `idAlumno`. El nuevo `idx_fecha` cubre búsquedas que filtran solo por `fecha` (ej: "¿quién asistió hoy?").

---

### 6. `dias_no_lectivos` — UNIQUE en `fecha` (🟠 Alto)

**Problema:**  
Sin restricción, se podía insertar el mismo día no lectivo dos veces. Aunque la UI no lo facilita directamente, proteger la integridad a nivel de BD es la práctica correcta.

**Solución en BD:**
```sql
ALTER TABLE dias_no_lectivos
  ADD UNIQUE KEY uq_fecha (fecha);
```

**Cambios en PHP:**  
No se requieren cambios; el INSERT ya existente fallará con error de clave duplicada si se intenta insertar la misma fecha, que es el comportamiento deseado.

---

## Script de migración completo (para BD en producción)

```sql
-- ============================================================
-- MIGRACIÓN BD Aula Matinal — 2026-04-30
-- Ejecutar en orden. Hacer backup antes.
-- ============================================================

-- 1. datos_aplicacion: añadir PK singleton
ALTER TABLE datos_aplicacion
  ADD COLUMN id tinyint unsigned NOT NULL DEFAULT 1 FIRST,
  ADD PRIMARY KEY (id),
  ADD CONSTRAINT chk_singleton CHECK (id = 1);

-- Fusionar las dos filas (ajustar valores a los reales):
DELETE FROM datos_aplicacion;
INSERT INTO datos_aplicacion (id, inicioCurso, finCurso, numDias, precioDia, precioMes)
VALUES (1, '2025-09-12', '2026-06-22', 11, 5.00, 50.00);

-- 2. asistencia: UNIQUE, tinyint, índice fecha
-- Verificar duplicados primero:
-- SELECT idAlumno, fecha, COUNT(*) FROM asistencia GROUP BY idAlumno, fecha HAVING COUNT(*) > 1;

ALTER TABLE asistencia
  MODIFY COLUMN reciboEmitido tinyint(1) NOT NULL DEFAULT 0,
  ADD UNIQUE KEY uq_alumno_fecha (idAlumno, fecha),
  ADD KEY idx_fecha (fecha);

-- 3. inscripciones: tinyint
ALTER TABLE inscripciones
  MODIFY COLUMN completada tinyint(1) NOT NULL DEFAULT 0;

-- 4. dias_no_lectivos: UNIQUE fecha
-- Verificar duplicados primero:
-- SELECT fecha, COUNT(*) FROM dias_no_lectivos GROUP BY fecha HAVING COUNT(*) > 1;

ALTER TABLE dias_no_lectivos
  ADD UNIQUE KEY uq_fecha (fecha);
```

---

## Puntos pendientes (no aplicados — requieren decisión)

### `recibos.totalDias` vs `recibos.diasAsistidos`
En todos los registros actuales, `totalDias = diasAsistidos`. Si la intención es que sean iguales siempre, `totalDias` es redundante y puede eliminarse con:
```sql
ALTER TABLE recibos DROP COLUMN totalDias;
```
Antes de hacerlo, confirmar que no hay lógica pendiente de implementar donde difieran (por ejemplo, "días laborables del mes" vs "días asistidos").

### Multi-curso escolar
La BD no distingue entre cursos escolares. Si la app va a usarse varios años, considerar añadir una columna `cursoEscolar YEAR` en `alumno` e `inscripciones` para poder separar y archivar datos por año.

### DNI único en inscripciones
Actualmente no hay restricción UNIQUE en `inscripciones.DNI`. Si una familia tiene dos hijos en el servicio, tendrán dos inscripciones con el mismo DNI del tutor, lo cual es un caso real. Por tanto, **no se recomienda** añadir UNIQUE en DNI a menos que el modelo de negocio garantice que cada DNI corresponde a un único alumno.
