← Proyecto backend completo

Sesión 47 · Semana 24

Especificar la ampliación final

Proyecto compartido. En el taller de Intermodular que abre esta semana has trabajado cerrar una candidata con evidencias de calidad. En Servidor continúas la implementación del mismo producto.

Se explica

25 minutos · explicación y demostración

El proyecto completo será la base de la ampliación final. Especificar significa acordar qué comportamiento nuevo se necesita antes de programarlo. Un criterio de aceptación describe una situación y un resultado observable; servirá para decidir si la ampliación está terminada.

Los cuatro pilares de una especificación técnica

Para blindar el diseño de tu backend, descompón el problema en cuatro matrices complementarias:

Estructura de descomposición del dominio
  1. 1. Actores y Roles: Quién opera el sistema
  2. 2. Casos de Uso: Qué acciones aportan valor real
  3. 3. Reglas Invariantes: Restricciones inmutables de negocio
  4. 4. Criterios Gherkin: Pruebas ejecutables Given-When-Then
  1. Mapa de Actores: Define las identidades que consumirán el backend:
    • Operario de campo: Reporta incidencias, sube ficheros binarios y consulta tareas asignadas.
    • Jefe de proyecto: Crea proyectos, asigna presupuestos, aprueba cambios de estado y consulta métricas.
    • Auditor externo: Solo lectura exhaustiva de históricos, trazas y registros de auditoría.
    • Administrador: Gestión de identidades, configuración global y reactivación de cuentas.
  2. Casos de Uso de Negocio: No son simples operaciones CRUD (“Crear Proyecto”), sino intenciones de negocio con reglas (“IniciarProyectoConPresupuestoAprobado”, “TransferirResponsabilidadDeTarea”).
  3. Reglas de Negocio Invariantes: Afirmaciones que la aplicación debe garantizar en el 100 % de los casos:
    • R1: Un proyecto no puede pasar a estado CERRADO si tiene tareas pendientes o incidencias críticas abiertas.
    • R2: El presupuesto consumido acumulado de las tareas no puede superar el presupuesto total asignado al proyecto.
    • R3: Ningún fichero adjunto puede sobrepasar los 5 MB ni contener extensiones no autorizadas.
  4. Criterios de Aceptación (Gherkin):
    Escenario: Cierre de proyecto con tareas sin finalizar
      Dado un proyecto en estado "EN_CURSO" que contiene 2 tareas "PENDIENTES"
      Cuando el usuario con rol "JEFE_PROYECTO" solicita cerrarlo
      Entonces el sistema rechaza la petición con código HTTP 409 Conflict
      Y devuelve un Problem Details indicando "No se puede cerrar un proyecto con tareas pendientes"
      Y el estado del proyecto permanece "EN_CURSO"

El peligro del ddl-auto en proyectos reales

Durante las primeras semanas del curso resultaba cómodo dejar que Hibernate generara y alterara las tablas automáticamente con ddl-auto=update.

En un proyecto profesional, delegar el esquema en Hibernate es una receta para el desastre:

  1. Hibernate no siempre crea los índices necesarios para claves foráneas o búsquedas complejas.
  2. Al renombrar un atributo en una clase Java, Hibernate no renombra la columna: crea una columna nueva y deja la antigua huérfana, provocando inconsistencia de datos.
  3. No permite controlar nombres de restricciones (constraints), claves foráneas ni tipos numéricos de precisión fija.

La ley del control del esquema

El esquema de base de datos es código fuente versionado.

El esquema debe definirse mediante scripts SQL explícitos (schema.sql o migraciones con Flyway/Liquibase). Hibernate debe configurarse en modo ddl-auto=validate para comprobar que las entidades coinciden con el esquema sin modificarlo nunca automáticamente.

Tipos de datos óptimos en PostgreSQL

Concepto de negocio Tipo SQL recomendado Tipo Java equivalente Justificación técnica
Identificadores primarios BIGINT GENERATED ALWAYS AS IDENTITY Long Clave autonumérica estándar SQL de 64 bits; evita desbordamientos de enteros de 32 bits.
Importes / Presupuesto NUMERIC(12, 2) BigDecimal Precisión decimal fija exacta. Los números de coma flotante (DOUBLE) sufren errores de redondeo binario intolerables en finanzas.
Cadenas cortas / Nombres VARCHAR(100) String Límites explícitos que protegen el almacenamiento y evitan inyección de textos arbitrariamente gigantes.
Descripciones / Texto largo TEXT String Almacenamiento eficiente sin límite prefijado para notas o descripciones extensas.
Marcas temporales TIMESTAMP WITH TIME ZONE Instant o OffsetDateTime Almacena el instante universal en UTC evitando ambigüedades por cambios de horario de verano/invierno.
Estados / Enums VARCHAR(30) Enum de Java Más flexible y legible en consultas SQL manuales que los ordinales numéricos (0, 1, 2).

Se trabaja

140 minutos · implementación guiada sobre el proyecto propio

Paso 1 · Retomar el proyecto y preparar la comprobación

  1. Abre la propuesta inicial, la documentación actual y el modelo de datos. Distingue funciones ya realizadas de la ampliación que vas a añadir.
  2. Elige casos nuevos que utilicen las capacidades aprendidas y escribe actor, datos de entrada, reglas y resultado. Los ejemplos del gestor se adaptan a esa ampliación.
  3. Localiza los scripts y paquetes existentes. Planifica cambios compatibles con los datos del proyecto; no ejecutes un esquema inicial como si la base de datos estuviera vacía.

Paso 2 · Definir los criterios de aceptación antes de implementar

Continúas el producto que llevas construyendo durante el curso. Los pasos de esta unidad te ayudan a especificar y completar su ampliación final; las decisiones de dominio y el código que las implementa siguen siendo tuyos.

El error más común del programador inexperto es abrir el IDE, crear entidades JPA a toda velocidad y empezar a programar endpoints basándose en suposiciones apresuradas.

  • A los tres días descubre que un proyecto no podía tener más de un responsable activo.
  • Descubre que una tarea cerrada no debía poder recibir adjuntos.
  • Descubre que los importes monetarios debían registrarse con impuestos desglosados y no como un simple número flotante.

Modificar el modelo de datos con la aplicación a medio construir cuesta diez veces más tiempo que resolver esas ambigüedades sobre el papel.

La ley del modelado previo

El código no toma decisiones de negocio: las ejecuta.

Toda duda o ambigüedad que no resuelvas durante la fase de especificación se convertirá en un bug en producción o en una refactorización dolorosa en la base de datos.

Paso 3 · Del enunciado al modelo conceptual

Diferenciamos dos tipos de objetos en nuestro modelo:

  • Entidades (con identidad propia en el tiempo): Proyecto, Tarea, Usuario, Incidencia, Adjunto. Si dos proyectos tienen el mismo nombre pero diferente ID, son proyectos distintos.
  • Objetos de Valor (inmutables, definidos por sus atributos): Dinero (importe + divisa), Coordenadas (latitud + longitud), PeriodoFechas (fechaInicio + fechaFin). Si dos objetos Coordenadas tienen la misma latitud y longitud, son idénticos.

Una entidad crítica nunca debería cambiar de estado mediante simples setters arbitrarios. Debe seguir un ciclo de vida estrictamente regulado:

[BORRADOR] ──(Aprobar presupuesto)──> [PLANIFICADO] ──(Asignar equipo)──> [EN_CURSO]
                                                                              │
               [CANCELADO] <──────(Cancelar con motivo justificado)────────────┤
                                                                              │
               [FINALIZADO] <─────(Verificar que no hay tareas pendientes)────┘
Caso de Uso Método HTTP Ruta propuesta Actor autorizado Códigos HTTP previstos
Registrar proyecto POST /api/v1/proyectos JEFE_PROYECTO, ADMIN 201, 400, 401, 403, 409
Aprobar presupuesto PATCH /api/v1/proyectos/{id}/presupuesto ADMIN 200, 400, 404, 409
Cerrar proyecto POST /api/v1/proyectos/{id}/cerrar JEFE_PROYECTO 200, 409, 404
Adjuntar informe POST /api/v1/tareas/{id}/adjuntos OPERARIO, JEFE_PROYECTO 201, 400, 413, 404

Paso 4 · Prueba de estrés del modelo conceptual

Somete tu modelo a tres preguntas trampa para verificar su solidez:

  1. Caso límite 1 (Concurrencia): Dos jefes de proyecto intentan cerrar el mismo proyecto exactamente en el mismo segundo. ¿Cómo previene tu modelo que se ejecute la notificación de cierre dos veces? (Requiere control de concurrencia optimista con @Version o bloqueos pesimistas).
  2. Caso límite 2 (Integridad de datos): Un usuario borra un proyecto que tiene 50 tareas y 10 incidencias históricas. ¿Se borran en cascada perdiendo la auditoría o se aplica un borrado lógico (Soft Delete)?
  3. Caso límite 3 (Degradación): El servicio externo de firma digital o meteorología no responde durante el alta. ¿Se bloquea el caso de uso o se guarda en estado pendiente de sincronización?

Paso 5 · Los tres vicios de una especificación mal escrita

Antes de redactar la tuya, aprende a reconocer lo que la invalida. Los tres aparecen siempre y los tres se arreglan con la misma medicina: un número.

1 · El requisito que no se puede comprobar
«El sistema debe ser rápido.» ¿Cuánto es rápido? Nadie puede determinar si se ha cumplido, de modo que no constituye un requisito, sino una aspiración. La versión utilizable es «el listado de proyectos responde en menos de 300 ms con 500 proyectos en la base de datos». Ahora se puede medir, y por tanto se puede aprobar o suspender.
2 · El requisito que esconde una decisión
«Un proyecto no puede cerrarse si tiene tareas pendientes.» Parece cerrado y no lo está: ¿qué es «pendiente»? ¿Cuenta una tarea bloqueada? ¿Y una cancelada? Enumera los estados concretos, o descubrirás la ambigüedad el día de la demostración.
3 · El requisito que solo describe el camino feliz
«El usuario adjunta una fotografía a la incidencia.» ¿Y si pesa 40 MB? ¿Y si es un .exe? ¿Y si el disco está lleno? Cada caso de uso necesita, como mínimo, un escenario de rechazo con su código HTTP. Si tu especificación solo tiene escenarios que terminan bien, no has especificado: has ilustrado.

Paso 6 · Redactar la especificación formal del proyecto

Elabora el documento de especificación formal de tu proyecto backend:

  1. Define los 4 actores y la matriz de control de acceso basada en roles (RBAC), con el mismo formato de la sesión 35: una fila por endpoint y método, una columna por rol y el código HTTP esperado en cada casilla. Esta tabla es el contrato de seguridad y va a ser lo que se pruebe en la sesión 51.
  2. Detalla al menos 6 casos de uso con sus escenarios Given-When-Then. Cada uno necesita un escenario feliz y al menos dos de rechazo, con su código de estado.
  3. Enumera 5 reglas de negocio invariantes que tu código garantizará siempre. Una invariante es una afirmación que nunca puede ser falsa: «la suma de costes de las tareas no supera el presupuesto del proyecto» lo es; «el usuario debería revisar el presupuesto» no lo es.
  4. Para cada invariante, escribe dónde va a vivir: ¿una restricción CHECK en PostgreSQL, una validación en el DTO, una comprobación en el servicio? Esta columna es la que convierte la especificación en un plan de trabajo, y es la que dicta el modelo de la sesión 47.
  5. Pasa la prueba de la ambigüedad: dale la especificación a otro equipo y pídeles que anoten cada punto donde tengan que suponer algo. Cada suposición es un hueco. Los que no cierres ahora se convertirán en una discusión en la semana 26, cuando ya no haya tiempo.
  6. Marca lo que no vas a hacer. Una especificación sin alcance excluido es una especificación que crecerá hasta que se acabe el plazo. «No habrá recuperación de contraseña por correo» es una decisión legítima y defendible, siempre que esté escrita.
Cómo saber que lo has terminado
Cada requisito se puede comprobar con una petición HTTP y un resultado esperado; cada caso de uso tiene escenarios de rechazo además del feliz; cada invariante tiene asignado el sitio donde se hará cumplir; otro equipo ha leído el documento y su lista de suposiciones está vacía o resuelta; y hay un apartado que dice qué queda fuera.

Formato de entrega

Incluye esta explicación en el registro de la sesión dentro del repositorio de GitHub, junto al código y las comprobaciones. La entrega es el enlace al repositorio y al commit de la sesión.

Arquitectura y modelo de datos

Paso 7 · Planificar el cambio de esquema y la organización de paquetes

El SQL del bloque es un ejemplo de esquema de llegada, no un script que debas ejecutar encima del esquema existente. Compara primero sus campos con tu ampliación y redacta un cambio incremental para tu base de desarrollo. Conserva los ids y datos previos y prepara valores para las nuevas columnas obligatorias antes de añadir la restricción. No actives inicialización automática de ese esquema completo en cada arranque. La aceptación consiste en ejecutar la nueva funcionalidad conservando los casos ya implementados.

-- Limpieza ordenada respetando integridad referencial
DROP TABLE IF EXISTS adjuntos CASCADE;
DROP TABLE IF EXISTS incidencias CASCADE;
DROP TABLE IF EXISTS tareas CASCADE;
DROP TABLE IF EXISTS proyectos CASCADE;
DROP TABLE IF EXISTS usuarios CASCADE;

-- Tabla de usuarios y credenciales
CREATE TABLE usuarios (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    password_hash VARCHAR(100) NOT NULL,
    nombre_completo VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    rol VARCHAR(30) NOT NULL,
    activo BOOLEAN NOT NULL DEFAULT TRUE,
    fecha_creacion TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- Tabla de proyectos
CREATE TABLE proyectos (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    codigo VARCHAR(20) NOT NULL UNIQUE,
    nombre VARCHAR(100) NOT NULL,
    descripcion TEXT,
    estado VARCHAR(30) NOT NULL DEFAULT 'PLANIFICADO',
    presupuesto_total NUMERIC(12, 2) NOT NULL CHECK (presupuesto_total >= 0),
    latitud NUMERIC(8, 5) NOT NULL,
    longitud NUMERIC(8, 5) NOT NULL,
    responsable_id BIGINT NOT NULL,
    version BIGINT NOT NULL DEFAULT 0, -- Para bloqueo optimista
    fecha_inicio DATE NOT NULL,
    fecha_fin_estimada DATE NOT NULL,
    fecha_creacion TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_proyectos_responsable FOREIGN KEY (responsable_id)
        REFERENCES usuarios (id) ON DELETE RESTRICT,
    CONSTRAINT chk_fechas_proyecto CHECK (fecha_fin_estimada >= fecha_inicio)
);

-- Tabla de tareas vinculadas
CREATE TABLE tareas (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    titulo VARCHAR(150) NOT NULL,
    prioridad VARCHAR(20) NOT NULL DEFAULT 'media',
    estado VARCHAR(30) NOT NULL DEFAULT 'PENDIENTE',
    coste_estimado NUMERIC(10, 2) NOT NULL DEFAULT 0.00 CHECK (coste_estimado >= 0),
    proyecto_id BIGINT NOT NULL,
    asignado_a BIGINT,
    CONSTRAINT fk_tareas_proyecto FOREIGN KEY (proyecto_id)
        REFERENCES proyectos (id) ON DELETE CASCADE,
    CONSTRAINT fk_tareas_asignado FOREIGN KEY (asignado_a)
        REFERENCES usuarios (id) ON DELETE SET NULL
);

-- Índices estratégicos para acelerar consultas y joins frecuentes
CREATE INDEX idx_proyectos_responsable ON proyectos (responsable_id);
CREATE INDEX idx_proyectos_estado ON proyectos (estado);
CREATE INDEX idx_tareas_proyecto ON tareas (proyecto_id);
CREATE INDEX idx_tareas_estado ON tareas (estado);

Creamos src/main/resources/data.sql con datos realistas para verificar inmediatamente el sistema:

-- Usuarios iniciales (contraseñas hasheadas con BCrypt para 'password123')
INSERT INTO usuarios (username, password_hash, nombre_completo, email, rol) VALUES
('admin', '<pega aquí tu hash de Password123!>', 'Administrador General', 'admin@empresa.com', 'ADMINISTRADOR'),
('jefe1', '<pega aquí tu hash de Password123!>', 'Elena Torres', 'elena.torres@empresa.com', 'JEFE_PROYECTO'),
('operario1', '<pega aquí tu hash de Password123!>', 'Carlos Ramos', 'carlos.ramos@empresa.com', 'DESARROLLADOR');

-- Proyecto inicial
INSERT INTO proyectos (codigo, nombre, descripcion, estado, presupuesto_total, latitud, longitud, responsable_id, fecha_inicio, fecha_fin_estimada) VALUES
('PRJ-2026-001', 'Parque Fotovoltaico Levante', 'Instalación de 500 paneles solares y subestación', 'EN_CURSO', 150000.00, 39.4699, -0.3763, 2, '2026-03-01', '2026-08-31');

-- Tareas asociadas
INSERT INTO tareas (titulo, prioridad, estado, coste_estimado, proyecto_id, asignado_a) VALUES
('Tendido de cableado de alta tensión', 'CRITICA', 'EN_CURSO', 12000.00, 1, 3),
('Cimentación de inversores solares', 'alta', 'FINALIZADA', 25000.00, 1, 3);
# Conexión a PostgreSQL
spring.datasource.url=jdbc:postgresql://localhost:5432/gestion_proyectos
spring.datasource.username=postgres
spring.datasource.password=postgres

# Inicialización por scripts SQL explícitos
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:schema.sql
spring.sql.init.data-locations=classpath:data.sql

# Hibernate solo valida: nunca modifica el esquema
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true

Paso 8 · Validación de esquema y restricciones en PostgreSQL

  1. Ejecuta la inicialización de la base de datos: Arranca el backend o ejecuta el script en tu cliente PostgreSQL preferido (DBeaver, psql).
  2. Comprueba las restricciones de integridad: Intenta insertar una fila que viole una regla de negocio SQL:
    -- Intento de proyecto con fecha fin anterior a fecha inicio (debe fallar)
    INSERT INTO proyectos (codigo, nombre, presupuesto_total, latitud, longitud, responsable_id, fecha_inicio, fecha_fin_estimada)
    VALUES ('ERR-01', 'Test Error', 5000, 39.0, -0.3, 2, '2026-05-10', '2026-05-01');
    Resultado esperado: PostgreSQL aborta la inserción con error de restricción de chequeo: ERROR: el nuevo registro para la relación «proyectos» viola la restricción «chk_fechas_proyecto».
  3. Comprueba el modo ddl-auto=validate: Si la aplicación arranca sin lanzar SchemaManagementException, significa que las clases @Entity de Java coinciden al 100 % con el esquema SQL real.

Paso 9 · Estructurar la arquitectura modular de paquetes

Organiza los paquetes mediante la herramienta de refactorización del IDE, moviendo una entidad funcional cada vez y actualizando imports. No copies las clases a otro paquete dejando las antiguas: Spring podría registrar dos controladores o dos entidades. Mantén la clase principal por encima de todos los paquetes. Ejecuta los tests después de cada movimiento y comprueba que la colección conserva las mismas rutas.

Por qué cambiamos de criterio justo ahora

Durante once unidades has organizado por capas: controller, service, repository, model, dto. Era lo correcto mientras aprendías qué hace cada capa, porque el paquete enseñaba la arquitectura.

Con el proyecto ya crecido, ese criterio empieza a estorbar: tocar «las tareas» obliga a abrir cinco paquetes distintos, y dos personas trabajando en dominios diferentes chocan en los mismos directorios. Organizar por funcionalidad junta lo que cambia junto. Las capas no desaparecen: siguen existiendo dentro de cada paquete de dominio. Lo que cambia es qué se pone en el primer nivel.

No hay una opción correcta: hay una que encaja mejor con el tamaño del proyecto y con cuánta gente lo toca a la vez. Poder argumentar eso es lo que se evalúa.

Además de mover paquetes, completa estas cuatro decisiones de modelo y anota la razón de cada una en el cuaderno de diseño:

  1. Tipos de los campos delicados. El dinero nunca es double: BigDecimal en Java y NUMERIC(12,2) en PostgreSQL, porque un double no puede representar exactamente 0,10 y los céntimos se pierden al sumar. Las fechas sin hora son LocalDate, no Date.
  2. Qué es obligatorio en la base de datos y no solo en el DTO. Una validación de Bean Validation protege de un cliente descuidado; un NOT NULL protege de un INSERT hecho a mano, de una migración y de un fallo tuyo. Las reglas que no pueden violarse nunca van en los dos sitios.
  3. Qué se borra en cascada y qué no. Borrar un proyecto, ¿borra sus tareas? ¿Y sus incidencias con adjuntos en disco? Decídelo explícitamente: por omisión, PostgreSQL rechazará el borrado y te encontrarás un 500 que en realidad era una regla de negocio sin declarar.
  4. Qué índices necesitas. Todo campo por el que filtres o busques (el codigo del proyecto, la clave ajena de tarea a proyecto, el username) merece un índice. Sin ellos, la paginación de la UD7 hace un recorrido completo de la tabla en cada página.

Paso 10 · Comprobar y registrar el resultado del proyecto

  1. Revisa cada criterio con un ejemplo permitido y otro rechazado. Otra persona debe poder entender cómo comprobarlo.
  2. Ensaya cualquier cambio de esquema sobre una copia de desarrollo y confirma que conserva los registros existentes y aplica las nuevas restricciones.

Ampliación si has completado el trabajo

Primero termina y verifica los pasos anteriores. Estos retos profundizan en el mismo contenido; no sustituyen la entrega ni obligan a iniciar otro proyecto.

Reto · Formalización de contratos con OpenAPI antes de codificar (API-First)

En la metodología API-First, antes de escribir una clase en Java, se redacta el archivo de especificación OpenAPI en formato YAML (openapi.yaml):

  1. Diseña el contrato del endpoint POST /api/v1/proyectos en YAML detallando el esquema del JSON de entrada, los campos obligatorios y los ejemplos de respuestas de error RFC 7807.
  2. Utiliza el editor Swagger Editor o la extensión OpenAPI de tu IDE para validar sintácticamente el contrato.
Objetivo mínimoLista de actores, casos de uso básicos y diagrama de entidades inicial.
Si lo tienesReglas invariantes formalizadas, máquinas de estado finitas y escenarios Gherkin.
RetoEnfoque API-First con contrato OpenAPI 3 preliminar redactado antes de programar.
Ver respuestas

1 · Porque «ModificarProyecto» es un saco genérico que suele saltarse validaciones específicas; nombres como «AprobarPresupuesto» o «ReasignarResponsable» encapsulan reglas precisas de transición.

2 · Las entidades se distinguen por un identificador único que perdura en el tiempo aunque cambien sus datos; los objetos de valor carecen de identidad propia y se definen exclusivamente por sus atributos inmutables.

3 · Dado (Given: contexto o estado inicial), Cuando (When: acción o evento disparador) y Entonces (Then: resultado o consecuencias esperadas).

4 · Evita transiciones ilegales o incoherentes en la base de datos (por ejemplo, que un proyecto cancelado pase directamente a finalizado sin pasar por revisión previa).

Reto · Versionado formal con Flyway

En lugar de recargar schema.sql en cada arranque, investiga la herramienta Flyway:

  1. Añade la dependencia org.flywaydb:flyway-core y flyway-database-postgresql.
  2. Estructura los scripts de migración en la carpeta src/main/resources/db/migration/:
    • V1__crear_esquema_inicial.sql
    • V2__anadir_indices_y_semillas.sql
  3. Comprueba cómo Flyway crea la tabla flyway_schema_history garantizando que las migraciones solo se aplican una vez y de forma estrictamente incremental.
Objetivo mínimoScript schema.sql con claves primarias, foráneas y tipos de datos óptimos.
Si lo tienesRestricciones de chequeo (CHECK), índices estratégicos y modo ddl-auto=validate.
RetoMigraciones incrementales versionadas gestionadas automáticamente con Flyway.
Ver respuestas

1 · Porque verifica que el modelo de clases Java coincide exactamente con las tablas existentes en la base de datos sin ejecutar ninguna instrucción DDL que pueda alterar o borrar datos.

2 · Porque si un usuario elimina accidentalmente un registro padre, la base de datos destruirá silenciosamente todos los registros hijos relacionados sin previo aviso ni trazabilidad.

3 · Acelera drásticamente las consultas con cláusulas JOIN y filtrados por el identificador del padre, evitando escaneos secuenciales completos (Full Table Scan) en tablas grandes.

4 · Porque almacena el instante temporal exacto normalizado en UTC, permitiendo que clientes ubicados en distintos husos horarios interpreten la fecha y hora sin discrepancias.

Cierre

15 minutos · resultado comprobable y explicación individual

Al terminar la sesión:

Cada requisito pendiente se vincula a una operación del producto y una comprobación.

Cada integrante explica una decisión del código apoyándose en una de las comprobaciones realizadas.