Diagrama UML de Clases — Modelo de Dominio (ExpressMX)
Resumen ejecutivo
Este documento presenta el modelo de dominio de ExpressMX como un diagrama UML de clases en notación Mermaid. ExpressMX es una plataforma mexicana de servicios a domicilio que articula cuatro subdominios: catálogo y geografía, operación de órdenes, finanzas y soporte ITSM (estilo ServiceNow), todo bajo un modelo RBAC zero-trust.
A diferencia del Modelo Entidad-Relación
(database-er-conceptual.md), que se enfoca en la persistencia, este diagrama
muestra clases con responsabilidades (atributos + métodos de dominio),
herencia entre los tres tipos de usuario, composición para los
agregados raíz (orden, ticket, mayor incidente) y multiplicidades
explícitas en cada asociación. Cada método del diagrama está respaldado por
una server action o función SQL que ya existe en el repositorio; la sección
"Trazabilidad" lo demuestra con referencias archivo:línea.
El diagrama cubre:
- 28 clases (18 mínimas requeridas + 10 adicionales para completar el dominio).
- 35 asociaciones con multiplicidades explícitas.
- 3 composiciones de agregado raíz.
- 1 jerarquía de herencia (Cliente / Prestador / Admin sobre Usuario).
- 26 métodos de dominio que reflejan reglas de negocio reales.
Diagrama (PlantUML)
Fuente: uml-class-diagram.puml
Renderizar con: plantuml -tpng uml-class-diagram.puml
2. Trazabilidad — clase a archivo fuente
Cada clase del diagrama mapea a una tabla del schema y, donde aplica, a una server action que implementa sus métodos de dominio. La columna Schema indica el origen físico de los atributos; la columna Métodos indica el archivo que materializa el comportamiento.
| Clase | Schema (tabla — archivo:línea) |
Métodos (acción — archivo:línea) |
|---|---|---|
| Usuario | 00.sql:96 (usuarios) |
apps/web/src/lib/dashboard/rbac-shared.ts:38 (tienePermiso) |
| Cliente | 00.sql:96 + filtro rol='cliente' |
apps/web/src/lib/dashboard/actions/clientes.ts (consultas) |
| Prestador | 00.sql:96 + filtro rol='prestador' |
apps/web/src/lib/dashboard/actions/prestadores.ts |
| Admin | 00.sql:96 + 00.sql:284 (usuarios_admin) |
apps/web/src/lib/dashboard/actions/equipo.ts:23 (asignarAdmin) |
| RolAdmin | 00.sql:135 (roles_admin) |
apps/web/src/lib/dashboard/actions/roles.ts |
| Permiso | 00.sql:145 (permisos) |
migrations/004_rbac_permisos_zero_trust.sql:1 (seed RBAC) |
| UsuarioAdmin | 00.sql:284 (usuarios_admin) |
apps/web/src/lib/dashboard/actions/equipo.ts:96 (cambiarRolAdmin), equipo.ts:133 (desactivarAdmin) |
| RolPermiso | 00.sql:295 (roles_permisos) |
apps/web/src/lib/dashboard/actions/roles.ts |
| Categoria | 00.sql:124 (categorias_servicio) |
apps/web/src/lib/dashboard/actions/catalogo.ts:112 (crearCategoria), catalogo.ts:142 (actualizarCategoria) |
| Servicio | 00.sql:192 (servicios) |
apps/web/src/lib/dashboard/actions/catalogo.ts:45 (actualizarServicio); contrato packages/types/src/api/v1/services.ts |
| ServicioPrestador | 00.sql:254 (servicios_prestador) |
apps/web/src/lib/dashboard/actions/prestadores.ts |
| Zona | 00.sql:154 (zonas_cobertura) |
apps/web/src/lib/dashboard/actions/zonas.ts:37 (crearZona), zonas.ts:68 (actualizarZona) |
| TarifaZona | 00.sql:334 (tarifas_zona) |
apps/web/src/lib/dashboard/actions/zonas.ts:136 (crearTarifa), zonas.ts:193 (toggleTarifa) |
| Direccion | 00.sql:206 (direcciones) |
apps/web/src/lib/dashboard/actions/cuenta.ts |
| Cupon | 00.sql:317 (cupones) |
apps/web/src/lib/dashboard/actions/cupones.ts:47 (crearCupon), cupones.ts:153 (detenerCupon) |
| Banner | 00.sql:359 (banners_promocionales) |
apps/web/src/lib/dashboard/actions/banners.ts:44 (crearBanner), banners.ts:135 (toggleBanner) |
| Invitacion | 00.sql:375 (invitaciones_prestadores) |
apps/web/src/lib/dashboard/actions/invitaciones.ts:26 (generarInvitacion), invitaciones.ts:68 (revocarInvitacion), invitaciones.ts:98 (extenderInvitacion); 00.sql:1001 (consumir_codigo_invitacion SQL) |
| Orden | 00.sql:395 (ordenes_servicio) |
apps/web/src/lib/dashboard/actions/operaciones.ts:15 (asignarOrden), operaciones.ts:78 (reasignarOrden), apps/web/src/lib/dashboard/actions/ordenes.ts:15 (cancelarOrden) |
| HistorialEstatus | 00.sql:426 (historial_estatus_orden) |
Trigger automático en cambio de estatus de ordenes_servicio |
| Evidencia | 00.sql:466 (evidencias_orden) |
apps/web/src/lib/dashboard/actions/operaciones.ts |
| Calificacion | 00.sql:452 (calificaciones) |
Mobile API apps/mobile-client/src/api/orders.ts |
| Pago | 00.sql:439 (pagos) |
apps/web/src/lib/dashboard/actions/operaciones.ts |
| Reembolso | 00.sql:565 (reembolsos) |
apps/web/src/lib/dashboard/actions/reembolsos.ts:13 (aprobarReembolso), reembolsos.ts:75 (marcarReembolsoProcesado), reembolsos.ts:43 (rechazarReembolso) — implementa Segregation of Duties |
| Factura | 00.sql:547 (facturas) |
apps/web/src/lib/dashboard/actions/cortes.ts |
| CortePago | 00.sql:531 (cortes_pago) |
apps/web/src/lib/dashboard/actions/cortes.ts:13 (marcarCorteRevisado), cortes.ts:49 (marcarCorteDepositado) |
| TransaccionPrestador | 00.sql:516 (transacciones_prestador) |
Trigger en cambio a estatus completada de orden |
| Ticket | 00.sql:492 + migrations/006_support_itsm.sql (extiende con tier/tipo) |
apps/web/src/lib/dashboard/actions/tickets.ts:18 (asignarTicket), tickets.ts:47 (cambiarEstatusTicket), tickets-escalate.ts:41 (escalarTicket), tickets-cerrar.ts:41 (cerrarTicket) |
| MensajeTicket | 00.sql:579 (mensajes_ticket) |
apps/web/src/lib/dashboard/actions/tickets.ts:100 (responderTicket) |
| MajorIncident | migrations/006_support_itsm.sql:374 (major_incidents) |
apps/web/src/lib/dashboard/actions/mim-crud.ts:40 (declararMim), mim-crud.ts:204 (vincularTicketAMim) |
| UpdateMIM | migrations/006_support_itsm.sql:397 (major_incident_updates) |
apps/web/src/lib/dashboard/actions/mim-crud.ts:103 (publicarUpdateMim) |
| KbArticle | migrations/006_support_itsm.sql:279 (kb_articles) |
apps/web/src/lib/dashboard/actions/kb-crud.ts:48 (crearKb), kb-crud.ts:107 (editarKb), kb-crud.ts:161 (publicarKb), kb-crud.ts:183 (despublicarKb) |
| SlaPolicy | migrations/006_support_itsm.sql:220 (sla_policies) |
Bootstrap automático por trigger al crear tickets_soporte |
| SlaCalendar | migrations/006_support_itsm.sql:201 (sla_calendars) |
Seed inicial en migrations/006_support_itsm.sql (1 calendario MX) |
2.1 Métodos no implementados (marcados como pendientes)
| Método declarado | Estado | Justificación |
|---|---|---|
Cliente.crearOrden(servicio, direccion) |
Cubierto desde mobile-client (apps/mobile-client/src/api/orders.ts), no como server action de dashboard. |
El dashboard administra órdenes ya existentes; la creación es un flujo del cliente móvil. |
Prestador.aceptarOrden(orden) |
No implementado como server action explícita. | Hoy el flujo es asignación push-by-admin (asignarOrden); la aceptación pull-by-provider es siguiente fase. |
Prestador.marcarDisponibilidad(...) |
No implementado en dashboard. | La tabla disponibilidad_prestador existe pero su mantenimiento se delega al onboarding y al perfil del prestador (mobile). |
Servicio.calcularPrecio(zona) |
Lógica embebida en triggers y en tarifas_zona. |
Se ejecuta al insertar la orden; no expone método público en server action. |
Banner.archivar() |
Cubierto por eliminarBanner (banners.ts:120), no hay un soft-archive. |
Decisión de diseño: el catálogo de banners es pequeño y la baja es definitiva. |
3. Notación
Símbolos UML utilizados en este diagrama:
| Símbolo | Significado |
|---|---|
<<abstract>> |
Clase abstracta. Usuario no se instancia directamente; siempre concretiza en Cliente, Prestador o Admin mediante el discriminador rol. |
<<asociativa>> |
Clase de asociación. Resuelve una relación N:M y carga atributos propios (ej. UsuarioAdmin, RolPermiso, ServicioPrestador, TarifaZona). |
<\|-- |
Herencia / generalización. La flecha apunta a la superclase. |
*-- |
Composición. El todo posee a la parte; la parte no existe sin el todo (ej. Orden *-- HistorialEstatus). Si se elimina la Orden, sus HistorialEstatus desaparecen (ON DELETE CASCADE). |
o-- |
Agregación. El todo agrega a la parte, pero la parte puede sobrevivir al todo. No se usa en este dominio: todas las relaciones parte-todo son composiciones porque las entidades débiles tienen ON DELETE CASCADE. Se documenta esta ausencia explícitamente. |
-- |
Asociación simple bidireccional. |
"1", "0..1", "0..*", "1..*" |
Multiplicidades en cada extremo de la asociación. |
+atributo: Tipo |
Atributo público. Tipos lógicos: String, Integer, UUID, Date, DateTime, Boolean, Decimal, JSONB (no se usan tipos PostgreSQL). |
+metodo(parametro: Tipo) Tipo |
Método de dominio público. Sólo se incluyen métodos con semántica de negocio (no getId(), no setters). |
3.1 Decisiones de modelado
- Herencia sobre
Usuario: aunque a nivel físico hay una sola tablausuarioscon discriminadorrol, a nivel de dominio cada subtipo tiene comportamiento distinto. La herencia se mapea con el patrón Single Table Inheritance. Calificacioncomo entidad fuerte: en el ER conceptual aparece como asociativa ternaria (calificador × calificado × orden), pero a nivel UML se modela como clase fuerte porque tiene identidad propia y reglas de unicidad sobre(orden_id, calificador_id).Reembolsocon dos métodos:aprobaryprocesarestán separados intencionalmente para reflejar la regla de Segregation of Duties (el aprobador debe ser distinto del procesador). El métodorechazarcierra el ciclo cuando el reembolso no procede.Ticketcontipo: el campotipo(incidente | solicitud | problema | cambio) proviene de la migración 006 ITSM y permite que la misma clase sirva para los cuatro flujos de ServiceNow sin sub-clases.
4. Cobertura del Caso Práctico
Cubre Problema 2 (Arquitectura) y Problema 3 (BD a nivel objeto) del Caso Práctico SOFTWARE 6.
- Problema 2 — Arquitectura: el diagrama materializa el modelo de dominio como capa de objetos, con composición / herencia / asociaciones; demuestra que el código del dashboard (server actions) implementa los métodos del modelo.
- Problema 3 — Base de datos a nivel objeto: la sección "Trazabilidad"
prueba que cada clase corresponde a una tabla persistente del schema y que
los atributos del dominio se preservan al pasar al modelo relacional. Los
tipos lógicos (
UUID,DateTime,JSONB, etc.) abstraen los tipos físicos de PostgreSQL para mantener el diagrama portable a otros motores.