0Mapa de la actividad

Sigue este orden: cada paso usa tipos del anterior, así nunca escribes una referencia a algo que aún no existe.

  1. Entiende el dominioGlosario, entidades, relaciones y estados del expediente (sección 1). Sin esto no sabrás qué campos poner.
  2. EnumeradosNo dependen de nada: son lo primero que compila.
  3. Objetos valorCoordenadas, DireccionPostal, Matricula: piezas pequeñas que usarán las entidades.
  4. Las doce entidadesEn tres bloques: catálogo → personas y vehículos → procedimiento.
  5. ComprobacionesNulos, imports prohibidos y compilación.
  6. ADRdocs/adr/0001-modelado-del-dominio.md con las cinco decisiones, sus alternativas descartadas y el porqué.

Entregable 1 · código

Los tipos del dominio en domain/model.

Entregable 2 · documento

docs/adr/0001-modelado-del-dominio.md con las cinco decisiones, alternativas descartadas y porqué.

Antes de empezarNecesitas la A0.2 terminada: el proyecto generado y la estructura de paquetes creada (config, domain.model, domain.exception, repository, service, controller, dto).
Qué es fuente y qué es propuestaEl enunciado, el glosario, las entidades, las relaciones, los estados del expediente y las reglas R1–R6 vienen literalmente de la fuente (apartados b, f e i). Los fragmentos de código plegados («Ver una propuesta») son propuestas construidas a partir de esa descripción: la fuente sólo da el código de Coordenadas, Gravedad e Infraccion. Intenta hacerlo tú antes de desplegarlas, y recuerda que la actividad te pide tomar y justificar decisiones: puedes discrepar de la propuesta si lo argumentas en el ADR.

1Entiende el dominio antes de teclear

MULTAGAL es el subsistema de tramitación de expedientes sancionadores de una administración de tráfico. Cubre el ciclo de vida completo de una denuncia: captación, tramitación, notificación, alegaciones, resolución, pago y, en su caso, detracción de puntos.

Glosario mínimo

TérminoSignificado
DenunciaActo por el que un agente o un dispositivo hace constar un hecho presuntamente infractor.
ExpedienteProcedimiento administrativo abierto a raíz de una denuncia. Tiene número, estado y plazos.
NotificaciónComunicación formal al interesado. Puede requerir varios intentos.
AlegaciónEscrito con el que el interesado se opone a la denuncia o aporta pruebas.
ResoluciónActo que pone fin al expediente: sanción firme, sobreseimiento o estimación de alegaciones.
FirmezaLa resolución ya no admite recurso ordinario. Sólo entonces se ejecuta.
Detracción de puntosDescuento del saldo del permiso; sólo ocurre con resolución firme.
PrescripciónExtinción de la responsabilidad por transcurso del plazo sin actuación administrativa.
SobreseimientoArchivo del expediente sin sanción.
Pronto pagoPago anticipado con bonificación, a cambio de renunciar a alegar.

Las doce entidades y sus datos

La columna «Datos» es tu lista de campos. Léela como un enunciado: cada coma es, casi siempre, un atributo.

EntidadDatos (según la fuente)
ConductorTitular de permiso de conducción. NIF/NIE, nombre, domicilio, fecha de expedición, saldo de puntos.
PermisoConduccionPermiso asociado a un conductor, con sus clases (A, B, C…), fechas de validez y estado.
VehiculoMatrícula, marca, modelo, tipo, fecha de matriculación, estado de ITV.
TitularidadRelación temporal N:M entre Conductor y Vehiculo (fecha de alta y de baja).
InfraccionCatálogo normativo: código, artículo del Reglamento, descripción, importe base, puntos a detraer, gravedad (leve / grave / muy grave).
DenunciaHecho denunciado: fecha y hora, lugar (con coordenadas), agente o dispositivo denunciante, infracción imputada, vehículo, conductor identificado (si lo hay).
ExpedienteTramitación administrativa de una denuncia: número de expediente, estado, importe, fechas del procedimiento.
NotificacionIntentos de notificación al interesado, con acuse y resultado.
AlegacionEscrito presentado por el interesado, con documentos adjuntos.
ResolucionActo administrativo que pone fin al expediente.
PagoAbonos totales o parciales, con posible bonificación por pronto pago.
RadarDispositivo de captación automática: identificador, ubicación, tipo, límite de velocidad controlado.

Relaciones

Del diagrama entidad-relación de la fuente. Te dice qué tipo apunta a qué otro (qué campo de tipo entidad lleva cada record/clase).

DeRelaciónACardinalidad
ConductorposeePermisoConduccion1 → 0..N
Conductores titular enTitularidad1 → 0..N
Vehiculofigura enTitularidad1 → 0..N
RadarcaptaDenuncia1 → 0..N
InfracciontipificaDenuncia1 → 0..N
Vehiculoimplicado enDenuncia1 → 0..N
Conductoridentificado enDenuncia1 → 0..N
DenunciaoriginaExpediente1 → 1
ExpedientegeneraNotificacion1 → 0..N
ExpedienterecibeAlegacion1 → 0..N
ExpedienteconcluyeResolucion1 → 0..1
ExpedienteliquidaPago1 → 0..N
Dos puntos que darán trabajoTitularidad es una N:M con atributos (fecha de alta y de baja): necesita un tipo propio, no basta con una lista dentro de Conductor. Además es temporal: un vehículo puede haber tenido varios titulares, y «¿quién era el titular el día de la denuncia?» no tiene respuesta trivial.
Conductor puede ser nulo en Denuncia. Un radar capta una matrícula, no una persona: la identificación del conductor es un paso posterior del procedimiento.

El expediente es una máquina de estados

Casi todas las reglas de negocio son restricciones sobre las transiciones válidas. De aquí salen los valores de EstadoExpediente.

INICIADO→NOTIFICADO→ALEGADO→RESUELTO→FIRME→PAGADO
DesdeHaciaCuándo
(inicio)INICIADO—
INICIADONOTIFICADOnotificación con acuse
INICIADOPRESCRITOvence el plazo sin notificar
NOTIFICADOPAGADO_BONIFICADOpago en 20 días naturales
NOTIFICADOALEGADOel interesado presenta alegación
NOTIFICADORESUELTOvence el plazo de alegaciones
ALEGADORESUELTOse contestan las alegaciones
RESUELTOFIRMEno cabe recurso ordinario
RESUELTOSOBRESEIDOse estiman las alegaciones
FIRMEPAGADO—

Estados finales: PAGADO, PAGADO_BONIFICADO, SOBRESEIDO, PRESCRITO.

Reglas de negocio que afectan al modelo

No las implementas ahora (eso llega desde la A0.4), pero tu modelo tiene que poder expresarlas. Si una regla necesita un dato, ese dato tiene que existir como campo.

ReglaEnunciadoQué exige del modelo
R1Bonificación del 50 % si el pago se produce dentro de los 20 días naturales siguientes a la notificación, con renuncia a alegaciones.Conocer la fecha de notificación y la del pago.
R2La detracción de puntos sólo se materializa cuando la resolución es firme.Poder saber si una Resolucion es firme.
R3Saldo cero ⇒ permiso suspendido, atómicamente.Saldo de puntos en Conductor; estado en PermisoConduccion.
R4Prescripción a los 3 meses (leves) o 6 meses (graves y muy graves) desde la fecha de comisión si no se ha notificado.Fecha de comisión y gravedad de la infracción.
R5Un expediente no puede resolverse mientras existan alegaciones pendientes de contestar.Saber si cada Alegacion está contestada.
R6La identificación del conductor es obligatoria para el titular del vehículo; su omisión genera una infracción autónoma muy grave.Un expediente puede generar otro: cuidado con las referencias circulares.

2Prepara los ficheros

Paquete obligatorio del proyecto: es.edu.multagal.domain.model (arquitectura en capas, apartado f). Diecinueve ficheros como mínimo.

text
src/main/java/es/edu/multagal/domain/model/
├── Gravedad.java               ← enumerados (4)
├── EstadoExpediente.java
├── EstadoPermiso.java
├── ResultadoNotificacion.java
├── Coordenadas.java            ← objetos valor (3)
├── DireccionPostal.java
├── Matricula.java
├── Infraccion.java             ← entidades (12)
├── Radar.java
├── Conductor.java
├── PermisoConduccion.java
├── Vehiculo.java
├── Titularidad.java
├── Denuncia.java
├── Expediente.java
├── Notificacion.java
├── Alegacion.java
├── Resolucion.java
└── Pago.java

3Enumerados: adiós a los String mágicos

Criterio 3: los enumerados sustituyen a los String mágicos. Si un campo sólo admite un conjunto cerrado de valores, es un enum.

Gravedad — viene dada en la fuente, cópiala tal cual:

java
package es.edu.multagal.domain.model;

public enum Gravedad { LEVE, GRAVE, MUY_GRAVE }

EstadoExpediente — sale directamente de la máquina de estados de la sección 1. Escríbelo tú: son nueve valores.

Ver una propuesta de EstadoExpediente
java
package es.edu.multagal.domain.model;

public enum EstadoExpediente {
    INICIADO,
    NOTIFICADO,
    ALEGADO,
    RESUELTO,
    FIRME,
    SOBRESEIDO,
    PAGADO,
    PAGADO_BONIFICADO,
    PRESCRITO
}

EstadoPermiso y ResultadoNotificacion — aquí la fuente no enumera los valores: los decides tú. Pistas de la fuente:

  • R3 habla de un permiso suspendido y de un conductor a 0 puntos con «el permiso activo».
  • La notificación «puede requerir varios intentos», cada uno «con acuse y resultado»: el resultado tiene que distinguir un intento que llega de uno que no.
Ver una propuesta (valores construidos, no de la fuente)
java
// EstadoPermiso.java — ACTIVO y SUSPENDIDO salen de R3; añade otros si los justificas
public enum EstadoPermiso { ACTIVO, SUSPENDIDO }

// ResultadoNotificacion.java — propuesta: la fuente sólo dice «con acuse y resultado»
public enum ResultadoNotificacion { ENTREGADA, RECHAZADA, AUSENTE, DESCONOCIDO }
Idea opcional: que Gravedad sepa su plazo de prescripción (R4)

El dominio contiene las «reglas invariantes del concepto, tipos, enums» (apartado f). El plazo de R4 depende sólo de la gravedad, así que puede vivir en el propio enum sin romper la regla de dependencia:

java
public enum Gravedad {
    LEVE(3), GRAVE(6), MUY_GRAVE(6);

    private final int mesesPrescripcion;

    Gravedad(int mesesPrescripcion) { this.mesesPrescripcion = mesesPrescripcion; }

    public int mesesPrescripcion() { return mesesPrescripcion; }
}

4Objetos valor

Un objeto valor no tiene identidad propia: dos Coordenadas con la misma latitud y longitud son la misma. Por eso encajan de forma natural con record (inmutable, con equals/hashCode por valor).

Coordenadas — dada en la fuente:

java
public record Coordenadas(double latitud, double longitud) { }

Matricula — la fuente te pregunta: ¿debe ser un tipo propio o basta con un String? Piensa qué gana un tipo propio: no puedes pasar un NIF donde se espera una matrícula, y el lugar natural para validar/normalizar el formato es su constructor. Esa es la decisión 4 del ADR.

Ver una propuesta de Matricula y DireccionPostal
java
// Matricula.java — el formato exacto no lo fija la fuente; aquí sólo se normaliza
public record Matricula(String valor) {
    public Matricula {                       // constructor compacto
        if (valor == null || valor.isBlank()) {
            throw new IllegalArgumentException("La matrícula no puede estar vacía");
        }
        valor = valor.replace(" ", "").replace("-", "").toUpperCase();
    }
}

// DireccionPostal.java — campos propuestos (la fuente sólo dice «domicilio»)
public record DireccionPostal(
        String via,
        String numero,
        String codigoPostal,
        String municipio,
        String provincia) { }

Fíjate en que IllegalArgumentException es de java.lang: validar en el dominio no exige importar nada de fuera.

5Las doce entidades, en tres bloques

Para cada campo aplica siempre las mismas tres preguntas: ¿qué tipo Java? · ¿puede ser null? · ¿cambiará con el tiempo?

Si el dato es…usa…nunca…
dinero (importe, pago)BigDecimaldouble / float
un día (plazo, validez, alta/baja)LocalDatejava.util.Date
un instante con hora (hecho denunciado)LocalDateTimejava.util.Date
un conjunto cerrado de valoresun enumString mágico
otra entidad del dominioese tipo (Vehiculo, Infraccion…)—

Bloque A · Catálogo: Infraccion y Radar

No dependen de ninguna otra entidad. Infraccion viene dada en la fuente:

java
public record Infraccion(
        String codigo,
        String articulo,
        String descripcion,
        BigDecimal importeBase,
        int puntos,
        Gravedad gravedad) { }

Ahora Radar: identificador, ubicación, tipo, límite de velocidad controlado. Pista: la ubicación ya tiene tipo (Coordenadas).

Ver una propuesta de Radar
java
public record Radar(
        String identificador,
        Coordenadas ubicacion,
        String tipo,              // la fuente no enumera los tipos: si los cierras, pásalo a enum
        int limiteVelocidad) { }  // km/h

Bloque B · Personas y vehículos

TipoCampos que pide la fuentePistas
ConductorNIF/NIE, nombre, domicilio, fecha de expedición, saldo de puntosdomicilio → DireccionPostal. El saldo cambia (R3): ¿record o clase?
PermisoConduccionconductor, clases (A, B, C…), fechas de validez, estadoestado → EstadoPermiso. También cambia (R3).
Vehiculomatrícula, marca, modelo, tipo, fecha de matriculación, estado de ITVmatrícula → Matricula.
Titularidadconductor, vehículo, fecha de alta, fecha de bajaLa N:M con atributos. ¿Qué significa una fecha de baja null?
Ver una propuesta del bloque B
java
public record Conductor(
        String nif,                     // NIF/NIE — en pruebas, siempre datos sintéticos
        String nombre,
        DireccionPostal domicilio,
        LocalDate fechaExpedicion,
        int saldoPuntos) { }

public record PermisoConduccion(
        Conductor conductor,
        Set<String> clases,             // "A", "B", "C"…
        LocalDate validoDesde,
        LocalDate validoHasta,
        EstadoPermiso estado) { }

public record Vehiculo(
        Matricula matricula,
        String marca,
        String modelo,
        String tipo,
        LocalDate fechaMatriculacion,
        LocalDate caducidadItv) { }      // una forma de expresar el «estado de ITV»

public record Titularidad(
        Conductor conductor,
        Vehiculo vehiculo,
        LocalDate fechaAlta,
        LocalDate fechaBaja) { }         // null = titularidad vigente

La propuesta usa record también para Conductor y PermisoConduccion: un cambio de saldo produce un nuevo valor en vez de modificar el existente. Es una decisión discutible a propósito — revisa la decisión 1 de la sección 7 y elige tú.

Bloque C · El procedimiento

TipoCampos que pide la fuentePistas
Denunciafecha y hora, lugar (con coordenadas), agente o dispositivo denunciante, infracción imputada, vehículo, conductor identificado (si lo hay)«fecha y hora» → ¿LocalDate o LocalDateTime? «agente o dispositivo»: uno de los dos quedará vacío.
Expedientenúmero, estado, importe, fechas del procedimiento; se origina en una denuncia (1:1)R1 necesita la fecha de notificación. Importe → BigDecimal.
Notificacionexpediente, intentos, acuse, resultadoresultado → ResultadoNotificacion.
Alegacionexpediente, escrito, documentos adjuntosR5 necesita saber si está contestada.
Resolucionexpediente, acto que pone finR2 necesita saber si es firme.
Pagoexpediente, abono total o parcial, posible bonificaciónR1: fecha del pago e importe.
Ver una propuesta de Denuncia y Expediente
java
public record Denuncia(
        LocalDateTime fechaHora,        // el hecho ocurre a una hora concreta
        Coordenadas lugar,
        String agente,                  // null si la capta un radar
        Radar radar,                    // null si la formula un agente
        Infraccion infraccion,
        Vehiculo vehiculo,
        Conductor conductor) { }        // null cuando la capta un radar

public record Expediente(
        String numero,
        EstadoExpediente estado,
        BigDecimal importe,
        LocalDate fechaNotificacion,    // null mientras no se haya notificado
        Denuncia denuncia) { }
Enlace con la A0.4Los ejemplos de la A0.4 construyen Expediente con cuatro argumentos (número, estado, importe, fecha de notificación). Si añades denuncia como quinto campo, ajusta esas llamadas; si no lo añades, explica en el ADR cómo representas la relación 1:1 con Denuncia.
Ver una propuesta de Notificacion, Alegacion, Resolucion y Pago
java
public record Notificacion(
        Expediente expediente,
        int intento,                    // 1, 2, 3… «puede requerir varios intentos»
        LocalDateTime fechaIntento,
        LocalDate fechaAcuse,           // null si no hubo acuse
        ResultadoNotificacion resultado) { }

public record Alegacion(
        Expediente expediente,
        LocalDate fechaPresentacion,
        String texto,
        List<String> documentos,        // nombres de los adjuntos (los binarios llegan en la UD5)
        boolean contestada) { }         // R5

public record Resolucion(
        Expediente expediente,
        LocalDate fecha,
        String fundamento,
        boolean firme) { }              // R2

public record Pago(
        Expediente expediente,
        LocalDate fecha,
        BigDecimal importe,
        boolean bonificado) { }         // R1

Las referencias van siempre del hijo al padre (Pago → Expediente). Con record inmutables no puedes crear un ciclo padre ↔ hijo, y eso te ahorra el problema de las «referencias circulares al serializar» que avisa R6.

6Comprueba antes de escribir el ADR

Tres comprobaciones rápidas. Hazlas ahora: si algo falla, cambia el código antes de documentarlo.

a) Los nulos, uno a uno

Recorre cada tipo y pregúntate qué campos pueden ser null legítimamente (porque el dominio lo permite, no por pereza). Apunta la lista para el ADR. Ejemplo de la fuente: Denuncia.conductor es null cuando la capta un radar.

b) Ningún import de fuera en domain

Es la regla de dependencia del apartado f: domain no depende de nada — ni de Spring, ni de JPA, ni de Jackson. Si tu Expediente importa org.springframework.*, el dominio ha dejado de ser el núcleo estable. Compruébalo mirando los import:

bash
grep -rnE "import (org\.springframework|jakarta\.persistence|com\.fasterxml)" src/main/java/es/edu/multagal/domain
powershell
Get-ChildItem -Recurse src/main/java/es/edu/multagal/domain -Filter *.java |
  Select-String -Pattern 'import (org\.springframework|jakarta\.persistence|com\.fasterxml)'

Resultado correcto: ninguna línea. Sólo deberías ver imports de java.* (java.math.BigDecimal, java.time.*, java.util.*).

c) Compila

bash
./mvnw clean package

7Las cinco decisiones del ADR

Criterio 5: el ADR justifica las decisiones, no sólo las describe. Para cada una: qué decidiste, qué alternativa descartaste y por qué.

1 · ¿record o clase?

¿Cuáles de estos tipos son inmutables por naturaleza y cuáles necesitarán mutabilidad más adelante? En la UD4, JPA exige constructor sin argumentos y campos mutables: ¿afecta eso a la decisión de ahora?

A favor de record

Catálogo (Infraccion), objetos valor y enumerados no cambian. Inmutable, equals por valor, menos código. Es el criterio de los ejemplos de la fuente.

A favor de clase

Lo que tiene estado que evoluciona (saldo de puntos, estado del permiso, estado del expediente). JPA (UD4) pedirá constructor vacío y campos mutables.

2 · Tipo de los importes

BigDecimal, nunca double: los importes son dinero y double no representa exactamente los decimales. Explica por qué en el ADR (y en la defensa oral: «¿Por qué los importes van en BigDecimal y no en double?»).

Una prueba que puedes citar en el ADR (ejemplo construido)
java
System.out.println(0.1 + 0.2);                          // 0.30000000000000004
System.out.println(new BigDecimal("0.1")
        .add(new BigDecimal("0.2")));                   // 0.3

3 · Tipo de las fechas

LocalDate frente a LocalDateTime: ¿cuál para la fecha de comisión de la infracción? ¿Y para el plazo de pronto pago? Pista: la denuncia registra «fecha y hora»; el plazo de R1 se cuenta en «días naturales». Nunca java.util.Date.

4 · Objetos valor frente a String

¿Matricula debe ser un tipo propio o basta con un String? Argumenta: seguridad de tipos, dónde vive la validación, legibilidad de las firmas.

5 · Nulos

¿Qué campos pueden ser null legítimamente? Recuerda que Denuncia.conductor lo es cuando la capta un radar. Usa la lista del paso 11.

Plantilla del ADR

Mismo formato simplificado que el ADR 0000 de la A0.2 (decisión, alternativas descartadas, consecuencias), con una entrada por decisión:

markdown
# ADR-0001 — Modelado del dominio
**Fecha:** AAAA-MM-DD
**Estado:** aceptada

## Contexto
Primer modelo Java del dominio MULTAGAL, sin persistencia (UD0).
En la UD4 se mapeará con JPA.

## Decisión 1 — record o clase
**Decisión:** ...
**Alternativa descartada:** ...
**Por qué:** ...

## Decisión 2 — Tipo de los importes
**Decisión:** BigDecimal
**Alternativa descartada:** double
**Por qué:** ...

## Decisión 3 — Tipo de las fechas
**Decisión:** ... para la fecha de comisión; ... para el plazo de pronto pago
**Alternativa descartada:** ...
**Por qué:** ...

## Decisión 4 — Matricula: tipo propio o String
**Decisión:** ...
**Alternativa descartada:** ...
**Por qué:** ...

## Decisión 5 — Campos que admiten null
| Campo | ¿Por qué puede ser null? |
|-------|--------------------------|
| Denuncia.conductor | La capta un radar: capta una matrícula, no una persona |
| ...   | ... |

## Consecuencias
Qué implica todo lo anterior para la UD3 (JDBC) y la UD4 (JPA).

✓Criterios de aceptación

Así se corrige. Repásalos uno a uno antes de dar la actividad por cerrada.

#CriterioCómo lo compruebas
1Están modeladas las doce entidades del dominioCuenta los ficheros del bloque A, B y C: 2 + 4 + 6 = 12
2Los importes usan BigDecimal y las fechas la API java.timeBusca double y Date en domain: sólo double en Coordenadas
3Los enumerados sustituyen a los String mágicosEstados, gravedad y resultado son enum
4Ningún tipo del paquete domain importa nada de SpringLa búsqueda del paso 12 no devuelve nada
5El ADR justifica las decisiones, no sólo las describeCada decisión tiene alternativa descartada y porqué
Protección de datosNo incorpores datos reales en ningún momento: los NIF, nombres, domicilios y matrículas de tus ejemplos y pruebas se generan sintéticamente y seudonimizados.

?Para la defensa oral

Preguntas de la autoevaluación de la UD0 relacionadas con esta actividad. Respóndelas sin mirar.

  • ¿Por qué el paquete domain no debe importar nada de Spring?
  • ¿Por qué los importes van en BigDecimal y no en double?
  • Enumera las reglas de dependencia entre las capas del proyecto. ¿Puede controller llamar a repository?
Ampliación opcionalDiagrama del dominio. Elabora el diagrama entidad-relación completo de MULTAGAL e inclúyelo en el README. Te servirá de guía durante todo el curso. (Se valora en el nivel de sobresaliente.)

Adaptado de apartado i — Actividades (A0.3), apartado b — El proyecto MULTAGAL y apartado f — La regla de dependencia.