Fix: al asociar carreras a un ITR, GET /carreras no mostraba el ITR
Descripción:
Qué
Arreglos en los catálogos (carreras, ITRs y grupos) que aparecieron al armar y probar la pantalla de Catálogos en el front.
1. Caché cruzado entre ITRs y Carreras
Después de asociar una carrera a un ITR, GET /carreras seguía mostrando la carrera sin ese ITR hasta reiniciar el backend. Al revés pasaba lo mismo: al renombrar o desactivar una carrera, GET /itrs seguía mostrando el nombre viejo.
Causa: cada listado incluye datos del otro (CarreraResponseDTO.itrs e ItrResponseDTO.carreras), pero cada service solo vaciaba su propio caché.
Arreglo: las escrituras de ItrService y CarreraService (crear, editar, desactivar y reactivar) ahora vacían los cachés itrs y carreras con @Caching.
2. Guardar un ITR borraba sus vínculos con carreras inactivas
GET /itrs solo devuelve las carreras ACTIVAS y el PUT reemplaza todas las asociaciones con carrerasIds. Como ningún cliente puede reenviar el id de una carrera que no ve, editar el ITR (aunque fuera sin tocar nada) dejaba INACTIVO en itr_carrera el vínculo con cada carrera inactiva. Al reactivar la carrera, ya no aparecía en el ITR.
Arreglo: asociarCarreras() ya no toca los vínculos con carreras inactivas. Para sacar una carrera inactiva de un ITR hay que reactivarla primero.
3. Editar una carrera no validaba el nombre repetido
Lo frenaba el UNIQUE de la tabla y el cliente recibía el 409 genérico ("conflicto con datos existentes"). Ahora responde "Ya existe una carrera con ese nombre.", igual que el alta. Cambiar solo las mayúsculas del propio nombre no cuenta como duplicado.
4. Nombres de carrera e ITR repetidos con otras mayúsculas o espacios
Se podían crear "Tecnicatura Revision Uno" y "tecnicatura revision uno", o un nombre con espacios al final.
-
API: el nombre se guarda recortado (
trim()) y la validación ignora mayúsculas.existsByNomCarreraIgnoreCaseyexistsByNomItrIgnoreCasereemplazan a los métodos anteriores. -
Base:
uk_nombre_carrerayuk_nom_itrcomparan exacto, así que se suman enproyecto_schema.sqllos índices únicosuk_nombre_carrera_normalizadoyuk_nom_itr_normalizadosobreLOWER(TRIM(nombre)), con la misma regla que la API.
5. Reactivar un grupo podía dejar dos grupos activos iguales
crear() y editar() no permiten dos grupos ACTIVOS coneneración, pero reactivar() no lo validaba. Si mientrasun grupo estaba inactivo se creaba otro igual, al reactivar el primero quedaban los dos activos. Ahora responde 409.
Tests
-
CatalogosCacheEvictionTest(nuevo): contexto mínimo d y los dos services reales, porque los tests con Mockito no pasan por el proxy de caché. Verifica que editar o desactivar un ITR vacíacarreras, y que editar o desactivar una carrera vacíaitrs. -
ItrServiceTest: el vínculo con una carrera inactiva sytrim()en el alta. -
CarreraServiceTest: nombre repetido al editar, cambio de solo mayúsculas permitido ytrim()en el alta. -
GrupoServiceTest: 409 al reactivar si ya hay otro gru - Suite completa: 847 tests, 0 fallas (con
SEED_DEMO_USERS=false; con los usuarios demo activados, los tests con contexto completo fallan contra el H2 vacío, igual que endev).
Cómo probar
- Crear la carrera X y después un ITR con X tildada. Sin reiniciar el backend,
GET /carrerastrae ese ITR enitrsde X. - Renombrar X: sin reiniciar,
GET /itrstrae el nombre - ITR con una carrera; desactivar la carrera; editar el ITR y guardar sin tocar nada; reactivar la carrera: sigue asociada al ITR.
- Crear la carrera "Tecnicatura Revision Uno" y después esponde 409. Lo mismo con un ITR.
- Editar una carrera con el nombre de otra: responde 409 con "Ya existe una carrera con ese nombre.".
- Crear el grupo "1A" (LTI, 2025) y desactivarlo; crear ivar el primero: responde 409.
Pendiente (para decidir en equipo, fuera de este MR)
-
ItrResponseDTO.carrerasdevuelve solo nombres. Pasar a{ idCarrera, nomCarrera }cambia el contrato que usa el front (StudentForm,StudentsListView, Catálogos). -
VER_CATEGORIAS_INSTANCIAyVER_CATEGORIAS_RECORDATORIOen el seed son solo de ADMINISTRADOR, y los roles que crean instancias ven vacíos los combos de categoría. - Reactivar responde 204 en carreras, grupos e ITRs, y 200 con cuerpo en categorías.
🗄️ Cambios en la base de datos
Dos índices únicos nuevos en proyecto_schema.sql. El scase por primera vez y ddl-auto=validate no los crea, asíque en bases existentes hay que crearlos a mano:
CREATE UNIQUE INDEX uk_nombre_carrera_normalizado ON proyecto.carreras (LOWER(TRIM(nom_carrera)));
CREATE UNIQUE INDEX uk_nom_itr_normalizado ON proyect;
Si ya hay nombres duplicados el CREATE falla. Para enco
SELECT LOWER(TRIM(nom_carrera)), array_agg(id_carrera
FROM proyecto.carreras GROUP BY 1 HAVING count(*) > 1;
SELECT LOWER(TRIM(nom_itr)), array_agg(id_itr)
FROM proyecto.itrs GROUP BY 1 HAVING count(*) > 1;
Relacionado
MR de front feature/gestion-catalogos (pantalla de Catálogos). Sin este MR, el front funciona igual, pero sigue mostrando "Sin ITR asignado" después de asociar una carrera a un ITR, y editar un ITR ivas.