Arquitectura Datos - misw-ds-en-nube/assistrack GitHub Wiki

Modelo de datos

Grupo 4 · Entrega 1 · Arquitectura general

1. Propósito

Modelo físico en PostgreSQL de los conceptos del modelo de dominio. Sigue tres criterios:

  • La base protege lo que puede expresar: llaves, unicidad, estados válidos, horas positivas y semanas que inician en lunes.
  • El historial se conserva: usuarios, períodos, espacios y vinculaciones se cierran o finalizan; no se borran.
  • Los archivos van fuera de la base: solo se guarda su storage_key, lo que permite pasar del volumen local a Cloud Storage sin cambiar tablas.

Las reglas que dependen de varias filas, de la fecha actual o del usuario autenticado las valida el backend (sección 5).

2. Diagrama entidad-relación

Modelo entidad-relación de la Entrega 1 en PostgreSQL

Notación. PK: llave primaria; FK: llave foránea; FK*: parte de una FK compuesta; UK: valor único. Línea continua: relación identificadora; punteada: no identificadora. Cada línea indica la FK de la tabla hija y su ON DELETE. La tabla gris (notificacion) está fuera de la Entrega 1.

Lectura. Las 12 tablas forman tres bloques: identidad y acceso, seguimiento operativo y reportes con IA. vinculacion es el centro: une una persona con un rol asignado y un espacio académico, y de ella dependen tareas, rectificaciones y el cálculo de horas. usuario es la tabla más referenciada, casi siempre como autor de una acción (*_por). Solo las dos relaciones de usuario_rol son identificadoras; las demás tablas tienen un UUID propio.

3. Entidades

Entidad Descripción Reglas relacionadas
usuario Persona que puede iniciar sesión RF-01, RF-02
rol / usuario_rol Catálogo de cuatro roles y su asignación a usuarios (N:M) RN-01, RF-02.2
periodo_academico Ciclo académico; habilita la creación de espacios RF-03.2, RN-07
espacio_academico Curso o proyecto de un profesor RN-06 a RN-09
vinculacion Persona con rol en un espacio, con horas contratadas RN-02, RN-10 a RN-19
vinculacion_rectificacion Historial de correcciones del administrador RN-12, RF-04.5
tarea Actividad de una semana dentro de una vinculación RN-20 a RN-34
adjunto Archivo de evidencia de una tarea RN-23
lote_reporte Solicitud de reportes de un profesor para una semana RN-41, RN-44
reporte_semanal PDF de una persona dentro de un lote RN-42 a RN-46
notificacion Aviso interno; tabla creada sin lógica en la Entrega 1 RN-38 a RN-40, RNF-04

Convenciones: llaves <tabla>_id de tipo UUID; autor <acción>_por; momento <acción>_en (TIMESTAMPTZ); día fecha_<nombre> (DATE); booleanos es_<nombre>; estados en VARCHAR con CHECK. Todas las columnas son NOT NULL salvo las marcadas.

3.1 Detalle por entidad

usuario

Columna Tipo Nulo Restricciones Descripción
usuario_id UUID No PK Identificador
correo VARCHAR(255) No UK Correo de inicio de sesión
contrasena_hash VARCHAR(255) No Hash bcrypt (RNF-03)
nombre_completo VARCHAR(150) No Nombre de la persona
creado_en TIMESTAMPTZ No Registro
actualizado_en TIMESTAMPTZ No Última modificación

rol

Columna Tipo Nulo Restricciones Descripción
rol_id UUID No PK Identificador
nombre VARCHAR(30) No UK; CHECK ADMIN, PROFESOR, MONITOR, ASISTENTE_GRADUADO Nombre del rol
creado_en TIMESTAMPTZ No Carga del catálogo

usuario_rol

Columna Tipo Nulo Restricciones Descripción
usuario_id UUID No PK, FK → usuario Usuario
rol_id UUID No PK, FK → rol Rol asignado
asignado_por UUID No FK → usuario Administrador que lo asignó
asignado_en TIMESTAMPTZ No Momento de la asignación

periodo_academico

Columna Tipo Nulo Restricciones Descripción
periodo_academico_id UUID No PK Identificador
codigo VARCHAR(10) No UK Código, por ejemplo 2026-10
estado VARCHAR(20) No CHECK INACTIVO, ACTIVO, CERRADO Estado
es_creacion_habilitada BOOLEAN No Permite crear espacios
fecha_inicio DATE No Inicio
fecha_cierre DATE No CHECK (fecha_cierre > fecha_inicio) Cierre
creado_en TIMESTAMPTZ No Registro
actualizado_en TIMESTAMPTZ No Última modificación

espacio_academico

Columna Tipo Nulo Restricciones Descripción
espacio_academico_id UUID No PK Identificador
periodo_academico_id UUID No FK → periodo_academico Período
profesor_id UUID No FK → usuario Profesor responsable
cerrado_por UUID Sí FK → usuario Quién lo cerró
nombre VARCHAR(150) No Nombre
tipo VARCHAR(20) No CHECK CURSO, PROYECTO Tipo (RN-06)
estado VARCHAR(20) No CHECK ACTIVO, CERRADO Estado (RN-08)
observaciones TEXT Sí Notas (RF-03.3)
fecha_inicio DATE No Inicio planeado
fecha_cierre DATE No CHECK (fecha_cierre >= fecha_inicio) Cierre planeado
cerrado_en TIMESTAMPTZ Sí CHECK ((estado = 'CERRADO') = (cerrado_en IS NOT NULL)) Cierre real
creado_en TIMESTAMPTZ No Registro
actualizado_en TIMESTAMPTZ No Última modificación

vinculacion

Columna Tipo Nulo Restricciones Descripción
vinculacion_id UUID No PK Identificador
usuario_id UUID No FK* → usuario_rol Persona vinculada
rol_id UUID No FK* → usuario_rol MONITOR o ASISTENTE_GRADUADO
espacio_academico_id UUID No FK → espacio_academico Curso o proyecto
creado_por UUID No FK → usuario Quién la creó
estado VARCHAR(20) No CHECK ACTIVA, FINALIZADA Estado
horas_semanales INTEGER No CHECK (horas_semanales > 0) Horas contratadas (RN-12)
creado_en TIMESTAMPTZ No Creación
finalizado_en TIMESTAMPTZ Sí Finalización
actualizado_en TIMESTAMPTZ No Última modificación

Índice único parcial (usuario_id, espacio_academico_id) WHERE estado = 'ACTIVA'. El profesor responsable (RN-11) se obtiene de espacio_academico.profesor_id.

vinculacion_rectificacion

Columna Tipo Nulo Restricciones Descripción
vinculacion_rectificacion_id UUID No PK Identificador
vinculacion_id UUID No FK → vinculacion Vinculación corregida
rol_anterior_id UUID No FK → rol Rol previo
rol_nuevo_id UUID No FK → rol Rol nuevo
rectificado_por UUID No FK → usuario Administrador
motivo TEXT No Razón
horas_semanales_anteriores INTEGER No Horas previas
horas_semanales_nuevas INTEGER No CHECK (> 0) Horas nuevas
rectificado_en TIMESTAMPTZ No Momento

Es un historial inmutable, por eso no tiene actualizado_en.

tarea

Columna Tipo Nulo Restricciones Descripción
tarea_id UUID No PK Identificador
vinculacion_id UUID No FK → vinculacion Vinculación (RN-21)
titulo VARCHAR(200) No Título
estado VARCHAR(20) No CHECK ABIERTO, EN_DESARROLLO, FINALIZADO Estado (RN-27)
descripcion TEXT No Descripción breve
observaciones TEXT Sí Notas
horas INTEGER No CHECK (horas > 0) Horas enteras (RN-24)
es_tardia BOOLEAN No Reporte tardío (RN-32)
fecha_inicio_semana DATE No CHECK (EXTRACT(ISODOW FROM fecha_inicio_semana) = 1) Lunes de la semana (RN-29)
creado_en TIMESTAMPTZ No Registro
actualizado_en TIMESTAMPTZ No Última modificación

adjunto

Columna Tipo Nulo Restricciones Descripción
adjunto_id UUID No PK Identificador
tarea_id UUID No FK → tarea Tarea
nombre_archivo VARCHAR(255) No Nombre original
storage_key VARCHAR(255) No UK Clave en el almacenamiento
tipo_mime VARCHAR(100) No Formato
tamano_bytes BIGINT No CHECK (tamano_bytes > 0) Tamaño
creado_en TIMESTAMPTZ No Carga

lote_reporte

Columna Tipo Nulo Restricciones Descripción
lote_reporte_id UUID No PK Identificador
solicitado_por UUID No FK → usuario Profesor solicitante
estado VARCHAR(20) No CHECK PENDIENTE, EN_PROCESO, COMPLETADO, CON_ERRORES Avance
mensaje_error TEXT Sí Fallo general
fecha_inicio_semana DATE No CHECK de lunes Semana reportada (RN-44)
solicitado_en TIMESTAMPTZ No Solicitud
iniciado_en TIMESTAMPTZ Sí Inicio del procesamiento
finalizado_en TIMESTAMPTZ Sí Fin
actualizado_en TIMESTAMPTZ No Última modificación

reporte_semanal

Columna Tipo Nulo Restricciones Descripción
reporte_semanal_id UUID No PK Identificador
lote_reporte_id UUID No FK → lote_reporte Lote
usuario_id UUID No FK → usuario Persona reportada
storage_key VARCHAR(255) Sí UK Clave del PDF; nula hasta generarse
estado VARCHAR(20) No CHECK PENDIENTE, GENERADO, ERROR Estado
mensaje_error TEXT Sí Fallo del reporte
generado_en TIMESTAMPTZ Sí Generación
creado_en TIMESTAMPTZ No Registro
actualizado_en TIMESTAMPTZ No Última modificación

UNIQUE (lote_reporte_id, usuario_id). El profesor y la semana se toman del lote.

notificacion

Columna Tipo Nulo Restricciones Descripción
notificacion_id UUID No PK Identificador
usuario_id UUID No FK → usuario Destinatario
mensaje TEXT No Texto
es_leida BOOLEAN No Leída
creado_en TIMESTAMPTZ No Generación
actualizado_en TIMESTAMPTZ No Última modificación

4. Relaciones y cardinalidades

Padre Hija FK Cardinalidad Tipo ON DELETE
usuario usuario_rol usuario_id 1 : 1..* Identificadora CASCADE
rol usuario_rol rol_id 1 : 0..* Identificadora RESTRICT
usuario_rol vinculacion (usuario_id, rol_id) 1 : 0..* No identificadora RESTRICT
espacio_academico vinculacion espacio_academico_id 1 : 0..* No identificadora RESTRICT
periodo_academico espacio_academico periodo_academico_id 1 : 0..* No identificadora RESTRICT
usuario espacio_academico profesor_id 1 : 0..* No identificadora RESTRICT
vinculacion tarea vinculacion_id 1 : 0..* No identificadora CASCADE
vinculacion vinculacion_rectificacion vinculacion_id 1 : 0..* No identificadora CASCADE
tarea adjunto tarea_id 1 : 0..* No identificadora CASCADE
lote_reporte reporte_semanal lote_reporte_id 1 : 1..* No identificadora CASCADE
usuario reporte_semanal usuario_id 1 : 0..* No identificadora RESTRICT
usuario lote_reporte solicitado_por 1 : 0..* No identificadora RESTRICT
usuario notificacion usuario_id 1 : 0..* No identificadora CASCADE
usuario usuario_rol asignado_por 1 : 0..* No identificadora RESTRICT
usuario espacio_academico cerrado_por 0..1 : 0..* No identificadora RESTRICT
usuario vinculacion creado_por 1 : 0..* No identificadora RESTRICT
usuario vinculacion_rectificacion rectificado_por 1 : 0..* No identificadora RESTRICT
rol vinculacion_rectificacion rol_anterior_id, rol_nuevo_id 1 : 0..* No identificadora RESTRICT

CASCADE se usa solo cuando la hija no existe sin su padre (composición); RESTRICT, en el resto, para proteger el historial. Las reglas se combinan: borrar un usuario con vinculaciones falla porque el RESTRICT de vinculacion bloquea la cascada sobre usuario_rol.

5. Restricciones de integridad

Restricción Dónde se aplica (BD / código) Regla
Estados y tipos válidos BD: CHECK en cada columna estado y tipo RN-06, RN-08, RN-27, RN-28
Horas enteras positivas BD: INTEGER + CHECK (> 0) RN-24
Semana inicia en lunes BD: CHECK sobre fecha_inicio_semana RN-29
La persona tiene el rol con que se vincula BD: FK compuesta → usuario_rol RN-02, RN-11
Una vinculación activa por persona y espacio BD: índice único parcial RN-13
Un reporte por persona y lote BD: UNIQUE (lote_reporte_id, usuario_id) RN-42
Rol de la vinculación es MONITOR o ASISTENTE_GRADUADO Código: C5 RN-02
Horas contratadas inmutables, salvo rectificación Código: C5 RN-12
Límites de 22 h, 3 monitorías, 12 h y 40 % Código: C5, en una transacción con pg_advisory_xact_lock(usuario_id) RN-14 a RN-19
Creación de espacios solo en período ACTIVO y habilitado Código: C3, C4 RN-07
Espacio cerrado en solo consulta Código: C4, C5, C6 RN-09
Edición solo en semana activa; tardías inmutables Código: C6 (America/Bogota) RN-30 a RN-34
Visibilidad por rol y propiedad Código: C1 y componente dueño del dato RN-03 a RN-05

Índices sobre FK: vinculacion (usuario_id, estado) y (espacio_academico_id); espacio_academico (profesor_id) y (periodo_academico_id); tarea (vinculacion_id, fecha_inicio_semana); adjunto (tarea_id); vinculacion_rectificacion (vinculacion_id); reporte_semanal (usuario_id); lote_reporte (solicitado_por, fecha_inicio_semana); notificacion (usuario_id, es_leida).

6. Almacenamiento de archivos

Archivo Clave Escribe / lee
Adjunto adjuntos/{tarea_id}/{adjunto_id} C6
Reporte PDF reportes/{lote_reporte_id}/{usuario_id}.pdf C8, con el PDF que entrega el worker
  • En la Entrega 1, volumen Docker archivos montado en /data/archivos (api y worker). En las siguientes, Cloud Storage, sin cambios en las tablas.
  • Las claves se generan con UUID; el nombre del cliente se guarda solo en nombre_archivo.
  • Las descargas pasan por la API, que verifica permisos.
  • Al eliminar una tarea, la base borra sus adjuntos en cascada y el backend borra los archivos.

7. Migraciones y datos semilla

Migraciones SQL versionadas (subida y bajada), embebidas en el binario de la API y aplicadas al arrancar. Orden: identidad (rol, usuario, usuario_rol) → periodo_academico → espacio_academico → vinculacion y vinculacion_rectificacion → tarea y adjunto → lote_reporte y reporte_semanal → notificacion.

Datos semilla:

  • Los cuatro roles se cargan en la primera migración.
  • El administrador inicial lo crea el backend al arrancar, a partir de ADMIN_CORREO y ADMIN_CONTRASENA, porque la contraseña requiere hash bcrypt y no debe quedar en el repositorio.

Decisiones de diseño

D-03.1 · Identificadores UUID

  • Contexto: la API expone los identificadores en sus rutas.
  • Alternativas consideradas: enteros autoincrementales.
  • Justificación y compromisos: no revelan volumen ni son adivinables, y el backend los genera antes de insertar (útil para storage_key). Cuestan más espacio en índices.
  • Hipótesis: volumen moderado de datos por semestre.

D-03.2 · Vinculación referenciada a usuario_rol con FK compuesta

  • Contexto: la persona debe tener el rol con que se vincula (RN-02).
  • Alternativas consideradas: FK separadas a usuario y rol, validando solo en código.
  • Justificación y compromisos: la base garantiza la pareja persona-rol e impide retirar un rol en uso. Obliga a asignar el rol antes de vincular, y no excluye ADMIN o PROFESOR, que se valida en C5.
  • Hipótesis: el administrador asigna los roles operativos antes de que los profesores vinculen.

D-03.3 · Reglas de horas en el backend con bloqueo por persona

  • Contexto: RN-14 a RN-19 suman varias filas y pueden llegar solicitudes concurrentes para la misma persona.
  • Alternativas consideradas: triggers, aislamiento SERIALIZABLE, SELECT … FOR UPDATE sobre usuario.
  • Justificación y compromisos: la regla queda en el dominio de C5 y se prueba sin base de datos. pg_advisory_xact_lock serializa solo por persona y no bloquea una tabla de otro componente. Exige que solo C5 escriba vinculaciones.
  • Hipótesis: baja concurrencia sobre una misma persona.

D-03.4 · Estados como VARCHAR con CHECK

  • Contexto: siete columnas con valores cerrados.
  • Alternativas consideradas: ENUM de PostgreSQL, tablas de catálogo.
  • Justificación y compromisos: un CHECK se modifica fácilmente en una migración y no requiere uniones. Los valores se repiten en el código y se validan con pruebas.
  • Hipótesis: —

D-03.5 · Semana como fecha de su lunes

  • Contexto: la semana es la unidad de seguimiento (RN-20, RN-29).
  • Alternativas consideradas: tabla semana; año y número ISO.
  • Justificación y compromisos: un DATE con CHECK se compara, agrupa e indexa sin crear semanas por adelantado. La semana activa se calcula, no se guarda.
  • Hipótesis: zona horaria única America/Bogota.

D-03.6 · Historial sin borrado y archivos fuera de la base

  • Contexto: el seguimiento debe ser consultable (RN-20) y los archivos migrarán a Cloud Storage.
  • Alternativas consideradas: borrado lógico generalizado; archivos en BYTEA.
  • Justificación y compromisos: RESTRICT protege las entidades con historial, que se cierran o finalizan. La storage_key desacopla la base del almacenamiento. Base y almacenamiento no comparten transacción, así que puede quedar un archivo huérfano.
  • Hipótesis: los archivos huérfanos son raros y se limpian después.
⚠️ **GitHub.com Fallback** ⚠️