📝 2023_Desarrollo
← Volver

PRUEBAS SELECTIVAS UPM 2023 — BLOQUE III: DESARROLLO

Grupo C, Subgrupo C1 · Parte 2: Supuesto Práctico · 17 de junio de 2023
Cada pregunta vale 1 punto. Tiempo estimado de escritura: ~12 min/pregunta


CONTEXTO DEL SUPUESTO

Una universidad pública aprueba el voto por correo en las elecciones a Rector. Se asigna a la unidad de informática el desarrollo de la aplicación. El modelo de datos proporcionado es:

Persona          (DNI, NRP, Nombre, Apellidos)
DatosLaborales   (NRP, Categoría, Centro)
DatosVotacion    (NRP, Mesa, Centro)
SolicitudesVotoPorCorreo (DNI, Fecha, Estado(rechazado;aceptado;espera))
RecibidosVotosPorCorreo  (DNI, Fecha)
EntregadosVotosPorCorreo (DNI, Mesa, Fecha)

NRP = Número de Registro de Personal: identificador único del trabajador.


PREGUNTA 1 — Herencia en POO: concepto y ejemplo con el modelo de datos

Concepto de herencia

La herencia es uno de los pilares fundamentales de la Programación Orientada a Objetos (POO). Permite que una clase (clase hija o subclase) adquiera automáticamente los atributos y métodos de otra clase (clase padre o superclase), pudiendo extenderlos o sobreescribirlos (override).

Ventajas:
- Reutilización de código: la clase hija no repite los atributos y métodos ya definidos en el padre.
- Polimorfismo: un objeto de la clase hija puede tratarse como un objeto del tipo padre.
- Mantenimiento: los cambios en la clase padre se propagan automáticamente a las hijas.

La herencia expresa una relación "es un" (IS-A): si ClaseHija extends ClasePadre, decimos que una instancia de ClaseHija "es un" ClasePadre.

Justificación de la elección de entidades

Del modelo de datos, Persona y DatosLaborales comparten el campo NRP. Conceptualmente, un trabajador de la universidad es una Persona con datos laborales adicionales. Por tanto, modelamos:
- Persona como clase padre con los atributos comunes (DNI, NRP, Nombre, Apellidos).
- Trabajador como clase hija que hereda de Persona y añade los atributos laborales (Categoría, Centro).

Esta jerarquía tiene sentido semántico: todo trabajador es una persona, pero no toda persona es necesariamente un trabajador (podría haber estudiantes u otros roles).

Ejemplo en Java

// Clase padre
public class Persona {
    private String dni;
    private String nrp;
    private String nombre;
    private String apellidos;

    // Constructor
    public Persona(String dni, String nrp, String nombre, String apellidos) {
        this.dni       = dni;
        this.nrp       = nrp;
        this.nombre    = nombre;
        this.apellidos = apellidos;
    }

    // Getters
    public String getDni()       { return dni; }
    public String getNrp()       { return nrp; }
    public String getNombre()    { return nombre; }
    public String getApellidos() { return apellidos; }

    // Método que puede ser sobreescrito
    public String getInfo() {
        return "DNI: " + dni + " | Nombre: " + nombre + " " + apellidos;
    }
}

// Clase hija: hereda de Persona mediante 'extends'
public class Trabajador extends Persona {
    private String categoria;
    private String centro;

    // Constructor: llama al constructor del padre con 'super'
    public Trabajador(String dni, String nrp, String nombre,
                      String apellidos, String categoria, String centro) {
        super(dni, nrp, nombre, apellidos); // Inicializa los atributos heredados
        this.categoria = categoria;
        this.centro    = centro;
    }

    // Getters propios de la clase hija
    public String getCategoria() { return categoria; }
    public String getCentro()    { return centro; }

    // Sobreescritura (override) del método del padre
    @Override
    public String getInfo() {
        return super.getInfo() + " | Categoría: " + categoria + " | Centro: " + centro;
    }
}

// Uso
public class Main {
    public static void main(String[] args) {
        Trabajador t = new Trabajador("12345678A", "NRP001",
                                      "Ana", "García López",
                                      "Profesora Doctora", "ETSI Informática");
        System.out.println(t.getInfo());
        // Polimorfismo: un Trabajador puede referenciarse como Persona
        Persona p = t;
        System.out.println(p.getNombre()); // Accede a métodos heredados
    }
}

Ejemplo equivalente en PHP

class Persona {
    protected string $dni;
    protected string $nrp;
    protected string $nombre;
    protected string $apellidos;

    public function __construct(string $dni, string $nrp,
                                string $nombre, string $apellidos) {
        $this->dni       = $dni;
        $this->nrp       = $nrp;
        $this->nombre    = $nombre;
        $this->apellidos = $apellidos;
    }

    public function getInfo(): string {
        return "DNI: {$this->dni} | Nombre: {$this->nombre} {$this->apellidos}";
    }
}

class Trabajador extends Persona {
    private string $categoria;
    private string $centro;

    public function __construct(string $dni, string $nrp, string $nombre,
                                string $apellidos, string $categoria, string $centro) {
        parent::__construct($dni, $nrp, $nombre, $apellidos);
        $this->categoria = $categoria;
        $this->centro    = $centro;
    }

    public function getCategoria(): string { return $this->categoria; }
    public function getCentro(): string    { return $this->centro; }

    public function getInfo(): string {
        return parent::getInfo() . " | Categoría: {$this->categoria} | Centro: {$this->centro}";
    }
}

PREGUNTA 2 — Clases para gestionar el estado del voto por correo

Se define una clase principal GestorVotoCorreo que encapsula la lógica de consulta sobre el estado del voto por correo de un trabajador, usando su DNI como identificador.

Para simplificar el modelo, se asume acceso a una base de datos a través de un objeto $db (PDO en PHP). Se trabaja con PHP por claridad y brevedad.

<?php

/**
 * Clase que gestiona el estado del proceso de voto por correo
 * para un trabajador identificado por su DNI.
 */
class GestorVotoCorreo {

    private string $dni;
    private PDO    $db;

    public function __construct(string $dni, PDO $db) {
        $this->dni = $dni;
        $this->db  = $db;
    }

    /**
     * a) Comprueba si el trabajador ha SOLICITADO el voto por correo.
     * Existe registro en SolicitudesVotoPorCorreo para ese DNI.
     */
    public function haSolicitadoVoto(): bool {
        $stmt = $this->db->prepare(
            "SELECT COUNT(*) FROM SolicitudesVotoPorCorreo WHERE DNI = ?"
        );
        $stmt->execute([$this->dni]);
        return $stmt->fetchColumn() > 0;
    }

    /**
     * b) Comprueba si se le ha CONCEDIDO el voto por correo.
     * La solicitud existe y tiene Estado = 'aceptado'.
     */
    public function haSidoConcedidoVoto(): bool {
        $stmt = $this->db->prepare(
            "SELECT COUNT(*) FROM SolicitudesVotoPorCorreo
             WHERE DNI = ? AND Estado = 'aceptado'"
        );
        $stmt->execute([$this->dni]);
        return $stmt->fetchColumn() > 0;
    }

    /**
     * c) Comprueba si el voto ha sido RECIBIDO por correo.
     * Existe registro en RecibidosVotosPorCorreo para ese DNI.
     */
    public function haRecibidoVoto(): bool {
        $stmt = $this->db->prepare(
            "SELECT COUNT(*) FROM RecibidosVotosPorCorreo WHERE DNI = ?"
        );
        $stmt->execute([$this->dni]);
        return $stmt->fetchColumn() > 0;
    }

    /**
     * d) Comprueba si el voto ha sido ENTREGADO a la mesa correspondiente.
     * Existe registro en EntregadosVotosPorCorreo para ese DNI.
     */
    public function haEntregadoVoto(): bool {
        $stmt = $this->db->prepare(
            "SELECT COUNT(*) FROM EntregadosVotosPorCorreo WHERE DNI = ?"
        );
        $stmt->execute([$this->dni]);
        return $stmt->fetchColumn() > 0;
    }
}

// -----------------------------------------------
// Aplicación breve que utiliza las clases anteriores
// y saca la información por consola
// -----------------------------------------------

// Conexión PDO (ejemplo)
$db = new PDO("mysql:host=localhost;dbname=elecciones", "usuario", "password");

$dni = "12345678A";
$gestor = new GestorVotoCorreo($dni, $db);

echo "=== Estado del voto por correo para DNI: $dni ===" . PHP_EOL;
echo "a) ¿Ha solicitado el voto por correo?   " . ($gestor->haSolicitadoVoto()    ? "SÍ" : "NO") . PHP_EOL;
echo "b) ¿Le ha sido concedido el voto?       " . ($gestor->haSidoConcedidoVoto() ? "SÍ" : "NO") . PHP_EOL;
echo "c) ¿Ha sido recibido el voto?           " . ($gestor->haRecibidoVoto()       ? "SÍ" : "NO") . PHP_EOL;
echo "d) ¿Ha sido entregado a la mesa?        " . ($gestor->haEntregadoVoto()      ? "SÍ" : "NO") . PHP_EOL;

PREGUNTA 3 — Ampliación del modelo de datos para el escrutinio

Para gestionar el escrutinio del voto por correo se necesita modelar:
- Que cada mesa se compone de urnas.
- Que cada urna corresponde a una categoría laboral concreta.
- Que existen 4 categorías laborales con su ponderación en la votación final.
- Que cada urna acumula votos a distintos candidatos.

Modelo de datos ampliado

-- Entidades nuevas o modificadas marcadas con (**)

** Candidato (IdCandidato, Nombre, Apellidos)

** CategoriaLaboral (IdCategoria, Descripcion, Ponderacion)
     -- Valores posibles:
     --   1 | "Profesores doctores con vinculación permanente" | 0.51
     --   2 | "Resto del profesorado y personal investigador"  | 0.16
     --   3 | "Estudiantes"                                    | 0.24
     --   4 | "Personal de administración y servicios"         | 0.09

** Urna (IdUrna, Mesa, Centro, IdCategoria)
     -- FK: IdCategoria → CategoriaLaboral
     -- Una mesa tiene exactamente una urna por categoría laboral
     -- DatosVotacion (NRP, Mesa, Centro) ya relaciona al trabajador con su mesa

** ResultadoUrna (IdUrna, IdCandidato, NumVotos)
     -- FK: IdUrna     → Urna
     -- FK: IdCandidato → Candidato
     -- Almacena los votos obtenidos por cada candidato en cada urna
     -- Se actualiza conforme se van recibiendo y escrutando los votos por correo

Relación con el modelo original

Diagrama conceptual (texto)

CategoriaLaboral (1) ──── (N) Urna (N) ──── (N) ResultadoUrna (N) ──── (1) Candidato
                                │
                          Mesa + Centro
                                │
                         DatosVotacion
                                │
                         DatosLaborales (Categoría)

PREGUNTA 4 — Implementación del cálculo del escrutinio

Se implementa el método de escrutinio en la clase GestorEscrutinio. El cálculo pondera los votos de cada urna según el peso de su categoría y produce el resultado final por candidato.

Lógica del escrutinio ponderado:

Para cada candidato, el resultado ponderado es:

ResultadoPonderado(candidato) = Σ [ (VotosUrna / TotalVotosCategoria) × PonderacionCategoria ]

donde la suma recorre todas las categorías laborales.

<?php

class GestorEscrutinio {

    private PDO $db;

    public function __construct(PDO $db) {
        $this->db = $db;
    }

    /**
     * Calcula el resultado parcial del escrutinio conforme se van
     * registrando los votos en ResultadoUrna.
     * Devuelve un array asociativo [IdCandidato => porcentajePonderado]
     */
    public function calcularEscrutinio(): array {

        // 1. Obtener todos los candidatos
        $candidatos = $this->db->query(
            "SELECT IdCandidato FROM Candidato"
        )->fetchAll(PDO::FETCH_COLUMN);

        $resultados = [];

        foreach ($candidatos as $idCandidato) {
            $puntuacionTotal = 0.0;

            // 2. Para cada categoría laboral, calcular la contribución ponderada
            $stmt = $this->db->query(
                "SELECT cl.IdCategoria, cl.Ponderacion
                 FROM CategoriaLaboral cl"
            );
            $categorias = $stmt->fetchAll(PDO::FETCH_ASSOC);

            foreach ($categorias as $categoria) {
                $idCategoria  = $categoria['IdCategoria'];
                $ponderacion  = (float) $categoria['Ponderacion'];

                // Total de votos emitidos en TODAS las urnas de esta categoría
                $stmtTotal = $this->db->prepare(
                    "SELECT COALESCE(SUM(ru.NumVotos), 0)
                     FROM ResultadoUrna ru
                     JOIN Urna u ON ru.IdUrna = u.IdUrna
                     WHERE u.IdCategoria = ?"
                );
                $stmtTotal->execute([$idCategoria]);
                $totalVotosCategoria = (int) $stmtTotal->fetchColumn();

                if ($totalVotosCategoria === 0) {
                    continue; // Sin votos en esta categoría todavía
                }

                // Votos del candidato en las urnas de esta categoría
                $stmtVotos = $this->db->prepare(
                    "SELECT COALESCE(SUM(ru.NumVotos), 0)
                     FROM ResultadoUrna ru
                     JOIN Urna u ON ru.IdUrna = u.IdUrna
                     WHERE u.IdCategoria = ? AND ru.IdCandidato = ?"
                );
                $stmtVotos->execute([$idCategoria, $idCandidato]);
                $votosCandidatoCategoria = (int) $stmtVotos->fetchColumn();

                // Contribución ponderada de esta categoría
                $puntuacionTotal += ($votosCandidatoCategoria / $totalVotosCategoria)
                                    * $ponderacion;
            }

            $resultados[$idCandidato] = round($puntuacionTotal * 100, 4); // En %
        }

        // Ordenar de mayor a menor puntuación
        arsort($resultados);
        return $resultados;
    }

    /**
     * Registra o actualiza el resultado de una urna concreta
     * cuando se recibe el recuento de votos de esa urna.
     */
    public function registrarResultadoUrna(int $idUrna,
                                            int $idCandidato,
                                            int $numVotos): void {
        // INSERT o UPDATE (UPSERT)
        $stmt = $this->db->prepare(
            "INSERT INTO ResultadoUrna (IdUrna, IdCandidato, NumVotos)
             VALUES (?, ?, ?)
             ON DUPLICATE KEY UPDATE NumVotos = ?"
        );
        $stmt->execute([$idUrna, $idCandidato, $numVotos, $numVotos]);
    }
}

// Uso de ejemplo
$db = new PDO("mysql:host=localhost;dbname=elecciones", "usuario", "password");
$gestor = new GestorEscrutinio($db);

// Registrar votos de una urna
$gestor->registrarResultadoUrna(idUrna: 1, idCandidato: 1, numVotos: 120);
$gestor->registrarResultadoUrna(idUrna: 1, idCandidato: 2, numVotos: 80);

// Calcular y mostrar escrutinio parcial
$resultados = $gestor->calcularEscrutinio();
echo "=== ESCRUTINIO PARCIAL ===" . PHP_EOL;
foreach ($resultados as $candidato => $puntuacion) {
    echo "Candidato $candidato: {$puntuacion}%" . PHP_EOL;
}

PREGUNTA 5 — API RESTful para el proceso de votación

a) Interfaz de los métodos (endpoints)

Se definen los tres endpoints solicitados siguiendo las convenciones REST:

Operación Método HTTP Endpoint Descripción
Asignar centro y mesa a un trabajador PUT /api/trabajadores/{nrp}/votacion Asigna o actualiza el centro y mesa de votación del trabajador
Saber si una persona ha pedido voto por correo GET /api/trabajadores/{dni}/voto-correo Devuelve el estado de la solicitud de voto por correo
Actualizar estado de la petición de voto por correo PATCH /api/trabajadores/{dni}/voto-correo/estado Actualiza el campo Estado en SolicitudesVotoPorCorreo

Justificación de los métodos HTTP:
- GET: operación de solo lectura, sin efectos secundarios (idempotente).
- PUT: reemplaza/crea el recurso de votación completo para ese NRP.
- PATCH: actualiza parcialmente un recurso existente (solo el campo Estado), más apropiado que PUT para modificaciones parciales.

Códigos de respuesta HTTP relevantes:
- 200 OK: operación exitosa con cuerpo de respuesta.
- 204 No Content: operación exitosa sin cuerpo de respuesta (ej. actualización).
- 400 Bad Request: datos de entrada inválidos.
- 404 Not Found: recurso no encontrado.
- 409 Conflict: conflicto (ej. ya existe una solicitud).

b) Implementación en PHP

<?php
// Enrutamiento simplificado. En producción se usaría un framework
// como Symfony, Laravel o Slim.

header('Content-Type: application/json');

$method = $_SERVER['REQUEST_METHOD'];
$uri    = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$parts  = explode('/', trim($uri, '/'));
// Ejemplo URI: /api/trabajadores/12345678A/voto-correo

$db = new PDO("mysql:host=localhost;dbname=elecciones", "usuario", "password");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// ------------------------------------------------------------------
// ENDPOINT 1: PUT /api/trabajadores/{nrp}/votacion
// Asigna el centro y mesa de votación a un trabajador
// ------------------------------------------------------------------
if ($method === 'PUT' && isset($parts[2]) && isset($parts[3])
    && $parts[3] === 'votacion') {

    $nrp  = $parts[2];
    $body = json_decode(file_get_contents('php://input'), true);
    $mesa   = $body['mesa']   ?? null;
    $centro = $body['centro'] ?? null;

    if (!$mesa || !$centro) {
        http_response_code(400);
        echo json_encode(['error' => 'Se requieren los campos mesa y centro']);
        exit;
    }

    // Verificar que el trabajador existe
    $stmtCheck = $db->prepare("SELECT COUNT(*) FROM DatosLaborales WHERE NRP = ?");
    $stmtCheck->execute([$nrp]);
    if ($stmtCheck->fetchColumn() == 0) {
        http_response_code(404);
        echo json_encode(['error' => 'Trabajador no encontrado']);
        exit;
    }

    // UPSERT: insertar o actualizar DatosVotacion
    $stmt = $db->prepare(
        "INSERT INTO DatosVotacion (NRP, Mesa, Centro) VALUES (?, ?, ?)
         ON DUPLICATE KEY UPDATE Mesa = ?, Centro = ?"
    );
    $stmt->execute([$nrp, $mesa, $centro, $mesa, $centro]);

    http_response_code(204);
    exit;
}

// ------------------------------------------------------------------
// ENDPOINT 2: GET /api/trabajadores/{dni}/voto-correo
// Devuelve si la persona ha pedido el voto por correo y su estado
// ------------------------------------------------------------------
if ($method === 'GET' && isset($parts[2]) && isset($parts[3])
    && $parts[3] === 'voto-correo' && !isset($parts[4])) {

    $dni = $parts[2];

    $stmt = $db->prepare(
        "SELECT DNI, Fecha, Estado FROM SolicitudesVotoPorCorreo WHERE DNI = ?"
    );
    $stmt->execute([$dni]);
    $solicitud = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$solicitud) {
        http_response_code(200);
        echo json_encode(['solicitado' => false]);
        exit;
    }

    http_response_code(200);
    echo json_encode([
        'solicitado' => true,
        'fecha'      => $solicitud['Fecha'],
        'estado'     => $solicitud['Estado']
    ]);
    exit;
}

// ------------------------------------------------------------------
// ENDPOINT 3: PATCH /api/trabajadores/{dni}/voto-correo/estado
// Actualiza el estado de la solicitud de voto por correo
// ------------------------------------------------------------------
if ($method === 'PATCH' && isset($parts[4]) && $parts[4] === 'estado') {

    $dni  = $parts[2];
    $body = json_decode(file_get_contents('php://input'), true);
    $nuevoEstado = $body['estado'] ?? null;

    $estadosValidos = ['aceptado', 'rechazado', 'espera'];
    if (!in_array($nuevoEstado, $estadosValidos)) {
        http_response_code(400);
        echo json_encode(['error' => 'Estado no válido. Use: aceptado, rechazado, espera']);
        exit;
    }

    $stmt = $db->prepare(
        "UPDATE SolicitudesVotoPorCorreo SET Estado = ? WHERE DNI = ?"
    );
    $stmt->execute([$nuevoEstado, $dni]);

    if ($stmt->rowCount() === 0) {
        http_response_code(404);
        echo json_encode(['error' => 'Solicitud no encontrada para ese DNI']);
        exit;
    }

    http_response_code(204);
    exit;
}

// Ruta no encontrada
http_response_code(404);
echo json_encode(['error' => 'Endpoint no encontrado']);

PREGUNTA 6 — Método JSON con información completa del trabajador

a) Estructura de la respuesta JSON

{
  "trabajador": {
    "dni": "12345678A",
    "nrp": "NRP001",
    "nombre": "Ana",
    "apellidos": "García López",
    "datosLaborales": {
      "categoria": "Profesores doctores con vinculación permanente",
      "centro": "ETSI Informática"
    }
  }
}

Justificación de la estructura: Se anida datosLaborales dentro del objeto trabajador porque los datos laborales no tienen entidad propia desde el punto de vista del cliente de la API: son atributos complementarios del mismo recurso. El NRP es la clave que une ambas tablas (Persona y DatosLaborales) y no necesita repetirse en el JSON anidado al estar ya en el nivel raíz.

b) Clase Java para almacenar el JSON recibido

Se definen dos clases que reflejan la estructura JSON anidada, usando el patrón habitual con Jackson o Gson.

import com.fasterxml.jackson.annotation.JsonProperty;

// Clase interna: representa datosLaborales
public class DatosLaboralesDTO {

    @JsonProperty("categoria")
    private String categoria;

    @JsonProperty("centro")
    private String centro;

    // Getters y setters
    public String getCategoria()             { return categoria; }
    public void setCategoria(String c)       { this.categoria = c; }
    public String getCentro()                { return centro; }
    public void setCentro(String c)          { this.centro = c; }
}

// Clase interna: representa el objeto trabajador
public class TrabajadorDTO {

    @JsonProperty("dni")
    private String dni;

    @JsonProperty("nrp")
    private String nrp;

    @JsonProperty("nombre")
    private String nombre;

    @JsonProperty("apellidos")
    private String apellidos;

    @JsonProperty("datosLaborales")
    private DatosLaboralesDTO datosLaborales;

    // Getters y setters
    public String getDni()                           { return dni; }
    public void setDni(String d)                     { this.dni = d; }
    public String getNrp()                           { return nrp; }
    public void setNrp(String n)                     { this.nrp = n; }
    public String getNombre()                        { return nombre; }
    public void setNombre(String n)                  { this.nombre = n; }
    public String getApellidos()                     { return apellidos; }
    public void setApellidos(String a)               { this.apellidos = a; }
    public DatosLaboralesDTO getDatosLaborales()     { return datosLaborales; }
    public void setDatosLaborales(DatosLaboralesDTO d){ this.datosLaborales = d; }
}

// Clase raíz: envuelve el objeto trabajador (refleja la clave raíz del JSON)
public class RespuestaTrabajadorDTO {

    @JsonProperty("trabajador")
    private TrabajadorDTO trabajador;

    public TrabajadorDTO getTrabajador()           { return trabajador; }
    public void setTrabajador(TrabajadorDTO t)     { this.trabajador = t; }
}

// Uso con Jackson
// ObjectMapper mapper = new ObjectMapper();
// RespuestaTrabajadorDTO respuesta = mapper.readValue(jsonString, RespuestaTrabajadorDTO.class);
// String nombre = respuesta.getTrabajador().getNombre();

PREGUNTA 7 — REST vs. SOAP

API RESTful — Características

REST (Representational State Transfer) es un estilo arquitectónico (no un protocolo) para diseñar servicios web, basado en los principios de la web y el protocolo HTTP.

Sus características principales son:

  1. Sin estado (stateless): Cada petición del cliente al servidor contiene toda la información necesaria para procesarla. El servidor no almacena el estado de la sesión entre peticiones.
  2. Interfaz uniforme: Los recursos se identifican mediante URIs (ej. /api/trabajadores/123). Las operaciones se expresan con los verbos HTTP estándar: GET, POST, PUT, PATCH, DELETE.
  3. Representación de recursos: Los recursos pueden representarse en múltiples formatos, siendo JSON el más habitual, también XML, HTML, etc.
  4. Sistema de capas: El cliente no necesita saber si está conectado directamente al servidor final o a intermediarios (proxies, balanceadores de carga, cachés).
  5. Cacheabilidad: Las respuestas pueden marcarse como cacheables, mejorando el rendimiento.
  6. Código bajo demanda (opcional): El servidor puede enviar código ejecutable al cliente (ej. JavaScript).

Servicio SOAP — Características

SOAP (Simple Object Access Protocol) es un protocolo estándar basado en XML para el intercambio de mensajes estructurados entre aplicaciones, definido por la W3C.

Sus características principales son:

  1. Basado en XML: Todos los mensajes (peticiones y respuestas) se encapsulan en un sobre SOAP (<Envelope>) con cabecera (<Header>) y cuerpo (<Body>), siempre en XML.
  2. Independiente del transporte: Puede funcionar sobre HTTP, SMTP, TCP u otros protocolos, aunque HTTP es el más común.
  3. Descripción formal con WSDL: Los servicios se describen mediante un documento WSDL (Web Services Description Language) que especifica las operaciones disponibles, sus parámetros y tipos de datos. Es un contrato formal y estricto.
  4. Estándares WS-*: Cuenta con extensiones estandarizadas para seguridad (WS-Security), transacciones (WS-Transaction), mensajería confiable (WS-ReliableMessaging), etc.
  5. Fuertemente tipado: El contrato WSDL define con precisión los tipos de datos, facilitando la validación.

Diferencias entre REST y SOAP

Aspecto REST SOAP
Naturaleza Estilo arquitectónico Protocolo estándar
Formato de mensajes JSON (habitual), XML, otros XML obligatorio
Contrato formal Opcional (OpenAPI/Swagger) WSDL obligatorio
Verbos/operaciones Usa verbos HTTP (GET, POST, PUT…) Solo POST; las operaciones van en el cuerpo XML
Rendimiento Mayor (JSON más ligero que XML) Menor (overhead XML + cabeceras)
Estado Sin estado (stateless) Puede mantener estado
Seguridad HTTPS + tokens (JWT, OAuth) WS-Security (estándar integrado)
Facilidad de uso Más sencillo, ampliamente adoptado en web Más complejo; habitual en entornos empresariales
Casos de uso típicos APIs web públicas, apps móviles, microservicios Servicios bancarios, ERP, entornos corporativos que requieren contratos estrictos

PREGUNTA 8 — ORM vs. acceso directo a base de datos

Un ORM (Object-Relational Mapper) como Hibernate (Java) o Doctrine (PHP) es una capa de abstracción que mapea objetos de programación a tablas relacionales, permitiendo trabajar con la base de datos usando el lenguaje orientado a objetos sin escribir SQL directamente.

Tres diferencias principales (ORM vs. acceso directo)

1. Abstracción del SQL y portabilidad

// Con Hibernate (ORM) — sin SQL
Persona p = session.get(Persona.class, 1L);

// Sin ORM — SQL directo
PreparedStatement ps = conn.prepareStatement("SELECT * FROM Persona WHERE id = ?");
ps.setLong(1, 1L);
ResultSet rs = ps.executeQuery();

2. Productividad y mantenimiento

3. Gestión automática de relaciones, caché y transacciones

Consideración adicional — Rendimiento:
El acceso directo a base de datos puede ser más eficiente para consultas muy complejas o de alto rendimiento, ya que el desarrollador tiene control total sobre el SQL generado. Los ORM pueden generar consultas subóptimas (problema N+1) si no se configuran correctamente, aunque ofrecen mecanismos como lazy loading, eager loading y la caché para mitigarlo.


PREGUNTA 9 — Anotaciones Hibernate en la clase Persona

a) Cómo se crearía la tabla Persona

Hibernate (con su herramienta de generación de esquemas, configurada mediante la propiedad hibernate.hbm2ddl.auto) leerá las anotaciones de la clase Persona y generará automáticamente la instrucción SQL CREATE TABLE correspondiente en la base de datos configurada.

El resultado sería equivalente a ejecutar el siguiente DDL:

CREATE TABLE Persona (
    id        BIGINT       NOT NULL AUTO_INCREMENT,  -- @Id + @GeneratedValue IDENTITY
    DNI       VARCHAR(255),                           -- @Column(name = "DNI")
    NRP       VARCHAR(255),                           -- @Column(name = "NRP")
    Nombre    VARCHAR(255),                           -- @Column(name = "Nombre")
    Apellidos VARCHAR(255),                           -- @Column(name = "Apellidos")
    PRIMARY KEY (id)
);

La tabla se crea con el nombre Persona (indicado en @Table), con una clave primaria autoincremental id y una columna por cada campo anotado con @Column.

b) Significado de cada anotación

@Entity
Indica a Hibernate que esta clase Java es una entidad persistente, es decir, que sus instancias deben ser gestionadas por el ORM y mapeadas a filas de una tabla en la base de datos. Es la anotación fundamental que activa el mapeo ORM para esa clase.

@Table(name = "Persona")
Especifica el nombre de la tabla en la base de datos a la que se mapea esta entidad. Si se omite, Hibernate usa el nombre de la clase Java como nombre de tabla por defecto. Con esta anotación se puede hacer que el nombre de la clase Java (Persona) y el nombre de la tabla (Persona) sean independientes.

@Id
Marca el campo como clave primaria de la entidad y de la tabla. Hibernate usará este campo para identificar unívocamente cada instancia y para las operaciones de búsqueda, actualización y borrado.

@GeneratedValue(strategy = GenerationType.IDENTITY)
Define la estrategia de generación del valor de la clave primaria. Con IDENTITY, Hibernate delega la generación del valor en la propia base de datos, utilizando la funcionalidad de autoincremento (AUTO_INCREMENT en MySQL, SERIAL en PostgreSQL). Otras estrategias posibles son SEQUENCE, TABLE y AUTO.

@Column(name = "DNI") (y análogamente las demás)
Mapea el atributo Java al nombre de columna especificado en la tabla. Si los nombres de atributo y columna coinciden, esta anotación puede omitirse; pero al especificarla se puede usar un nombre de columna diferente al del campo Java, o configurar restricciones adicionales como nullable = false, length = 9, unique = true, etc.


PREGUNTA 10 — Completar el método insertarPersona con Hibernate

El método insertarPersona debe crear un objeto Persona, asignarle los valores recibidos como parámetros, y persistirlo en la base de datos usando la sesión de Hibernate ya abierta.

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import paquete.clase.Persona;
import paquete.util.HibernateUtil;

public class PersonaDAO {

    public void insertarPersona(String dni, String nrp,
                                String nombre, String apellidos) {

        SessionFactory sessionFactory = HibernateUtil.getSessionFactory();

        try (Session session = sessionFactory.openSession()) {
            Transaction transaction = session.beginTransaction();

            // ── CÓDIGO QUE SE PIDE IMPLEMENTAR ──────────────────────

            // 1. Crear una nueva instancia de la entidad Persona
            Persona persona = new Persona();

            // 2. Asignar los valores recibidos como parámetros
            persona.setDni(dni);
            persona.setNrp(nrp);
            persona.setNombre(nombre);
            persona.setApellidos(apellidos);

            // 3. Persistir el objeto en la base de datos
            //    session.save() o session.persist() insertan una nueva fila
            session.save(persona);

            // ── FIN DEL CÓDIGO IMPLEMENTADO ──────────────────────────

            transaction.commit();

        } catch (Exception e) {
            e.printStackTrace();
            // En una implementación real, se haría rollback explícito:
            // if (transaction != null) transaction.rollback();
        }
    }
}

Explicación de los tres pasos implementados:

  1. new Persona(): Se instancia un objeto Java de tipo Persona (entidad mapeada). En este punto no hay ninguna fila en la base de datos; el objeto está en estado transient (transitorio, no gestionado por Hibernate).

  2. setDni(), setNrp(), setNombre(), setApellidos(): Se asignan los valores a los atributos del objeto mediante los setters generados. El campo id no se asigna porque es autoincremental (@GeneratedValue IDENTITY); Hibernate lo obtendrá de la base de datos tras la inserción.

  3. session.save(persona): Hibernate transiciona el objeto al estado persistent (gestionado) y genera y ejecuta el INSERT INTO Persona (DNI, NRP, Nombre, Apellidos) VALUES (?, ?, ?, ?) correspondiente. El id generado por la base de datos se asigna automáticamente al campo id del objeto Java. El transaction.commit() ya estaba en el código base y consolida la transacción en la base de datos.

Nota: En versiones modernas de Hibernate (≥ 5.7 / JPA 2.2) se prefiere session.persist(persona) sobre session.save(), ya que persist() es el método estándar JPA. Ambos son válidos para este ejercicio.


Fin del examen · UPM 2023 · Bloque III: Desarrollo