Arquitectura y estructura de carpetas - MiguelAngelMarinMejia1/ArquitecturaDeSoftware-Proyecto GitHub Wiki

VisiΓ³n general

Hotel MDJ estΓ‘ organizado en capas concΓ©ntricas: el nΓΊcleo de negocio no conoce a quien lo consume. Sobre ese nΓΊcleo se apoyan dos capas de presentaciΓ³n independientes β€”el sitio web renderizado en servidor y la API REST en JSONβ€” que comparten exactamente la misma lΓ³gica.

                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                 β”‚            PRESENTACIΓ“N                      β”‚
                 β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
                 β”‚  β”‚  app  web      β”‚    β”‚ reservation.api  β”‚   β”‚
                 β”‚  β”‚  HTML + Django β”‚    β”‚  DRF / JSON      β”‚   β”‚
                 β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚                     β”‚
                             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β–Ό
                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                 β”‚            APLICACIΓ“N                        β”‚
                 β”‚   services.py        selectors.py            β”‚
                 β”‚   (comandos)         (consultas)             β”‚
                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β–Ό
                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                 β”‚            DOMINIO                           β”‚
                 β”‚   builders.py  policies.py  codes.py         β”‚
                 β”‚   exceptions.py                              β”‚
                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β–Ό
                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                 β”‚   PERSISTENCIA          INFRAESTRUCTURA      β”‚
                 β”‚   models.py             infra/factories.py   β”‚
                 β”‚                         infra/notifications  β”‚
                 β”‚                         infra/payments.py    β”‚
                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

La regla que sostiene el diseΓ±o: las dependencias apuntan siempre hacia adentro. web/ importa de reservation, pero reservation jamΓ‘s importa de web. Y dentro de reservation, solo el subpaquete api/ conoce Django Rest Framework.

Estructura

hotelMDJProject/
β”œβ”€β”€ manage.py
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ hotelMDJ/                     # configuraciΓ³n del proyecto
β”‚   β”œβ”€β”€ settings.py
β”‚   └── urls.py                   # '' β†’ web  |  'api/v1/' β†’ reservation.api
β”‚
β”œβ”€β”€ reservation/                  # NÚCLEO DE NEGOCIO
β”‚   β”œβ”€β”€ models.py                 # entidades de persistencia
β”‚   β”œβ”€β”€ services.py               # orquestaciΓ³n de casos de uso
β”‚   β”œβ”€β”€ selectors.py              # consultas de lectura
β”‚   β”œβ”€β”€ admin.py
β”‚   β”œβ”€β”€ domain/
β”‚   β”‚   β”œβ”€β”€ builders.py           # ReservationBuilder
β”‚   β”‚   β”œβ”€β”€ policies.py           # PricingPolicy, TaxPolicy, CapacityPolicy
β”‚   β”‚   β”œβ”€β”€ codes.py              # ConfirmationCodeGenerator
β”‚   β”‚   └── exceptions.py         # errores de dominio
β”‚   β”œβ”€β”€ infra/
β”‚   β”‚   β”œβ”€β”€ notifications.py      # BaseNotifier + implementaciones
β”‚   β”‚   β”œβ”€β”€ payments.py           # BaseGateway + implementaciΓ³n simulada
β”‚   β”‚   └── factories.py          # NotifierFactory, PaymentGatewayFactory
β”‚   β”œβ”€β”€ api/
β”‚   β”‚   β”œβ”€β”€ serializers.py
β”‚   β”‚   β”œβ”€β”€ views.py              # APIView
β”‚   β”‚   β”œβ”€β”€ urls.py
β”‚   β”‚   └── exception_handler.py  # dominio β†’ HTTP
β”‚   β”œβ”€β”€ fixtures/seed.json
β”‚   └── migrations/
β”‚
└── web/                          # PRESENTACIΓ“N HTML
    β”œβ”€β”€ views.py
    β”œβ”€β”€ urls.py
    β”œβ”€β”€ templates/web/
    └── static/web/

JustificaciΓ³n de cada decisiΓ³n

Por quΓ© dos apps y no una

La alternativa evidente era meter todo en reservation/. Se separΓ³ web/ por una razΓ³n concreta: hace imposible, por construcciΓ³n, que la lΓ³gica de negocio se filtre a la capa HTML. Si las plantillas y las vistas de pΓ‘gina vivieran junto a los servicios, nada impedirΓ­a que un dΓ­a alguien calculara un precio dentro de una vista. Con dos apps, esa dependencia serΓ­a un import cruzado visible en revisiΓ³n de cΓ³digo.

El beneficio secundario es que el nΓΊcleo queda listo para extraerse: si maΓ±ana el sitio web pasa a ser una SPA, se borra web/ y reservation/ sigue funcionando sin tocar una lΓ­nea.

Por quΓ© domain/ separado de models.py

El enunciado prohΓ­be lΓ³gica de negocio en mΓ©todos de los modelos. Pero el problema no es solo normativo: los modelos de Django estΓ‘n atados al ORM, y una regla de negocio escrita dentro de un modelo solo se puede probar levantando una base de datos.

En domain/ las reglas son clases planas de Python. PricingPolicy.quote() se prueba con objetos en memoria, sin migraciones y sin base de datos.

Por quΓ© services.py y selectors.py separados

Los comandos (que cambian estado) y las consultas (que solo leen) tienen necesidades distintas: los primeros necesitan transacciones, validaciones e invariantes; las segundas necesitan select_related y filtros eficientes. Mezclarlos produce clases que crecen sin control.

selectors.py concentra ademΓ‘s toda la lΓ³gica de solapamiento de fechas, que es la consulta mΓ‘s delicada del sistema y merece un ΓΊnico lugar donde razonarla.

Por quΓ© infra/ aislado

notifications.py y payments.py son adaptadores a mundos externos (SMTP, una pasarela de pago). Son la parte del sistema con mΓ‘s probabilidad de cambiar por razones ajenas al negocio. Aislarlos detrΓ‘s de las abstracciones BaseNotifier y BaseGateway significa que cambiar de proveedor de correo no toca ni un archivo de domain/ ni de services.py.

Por quΓ© api/ es un subpaquete y no un app

La API no es un contexto de negocio distinto: es otra forma de exponer el mismo. Ponerla como subpaquete deja explΓ­cito que depende de reservation, y concentra en un solo directorio todo lo que sabe de HTTP y de DRF.

Mapeo entre el diagrama de clases y el cΓ³digo

El diagrama UML declara operaciones dentro de las entidades. Al implementarlas se reubicaron en la capa que les corresponde, manteniendo la semΓ‘ntica:

OperaciΓ³n del diagrama ImplementaciΓ³n
Reserva.calcularTotal() domain/policies.py β†’ PricingPolicy.quote()
Reserva.confirmar() / cancelar() services.py β†’ ReservationService
Reserva.modificarFechas() Entrega 2
Habitacion.verificarDisponibilidad() selectors.py β†’ available_rooms(), is_room_available()
Habitacion.cambiarEstado() models.Room.Status + admin
TipoHabitacion.calcularPrecioTemporada() domain/policies.py β†’ SeasonalPricingPolicy
Cliente.registrarse() services.py β†’ CustomerService.get_or_create_profile()
Cliente.consultarHistorialReservas() reservation.customer.reservations (related name)
Pago.procesarPago() services.py β†’ PaymentService.charge()
Factura.generarFactura() services.py β†’ InvoiceService.issue()

Modelo de datos implementado

Se implementaron las seis clases del diagrama mΓ‘s una tabla intermedia:

Clase Estado
Customer Completa, con flujo de negocio y API
RoomType Completa, con flujo de negocio y API
Room Completa, con flujo de negocio y API
Reservation Completa, con flujo de negocio y API
ReservationRoom Tabla intermedia que materializa el 1..* del diagrama
Payment Persistencia + PaymentService con pasarela simulada
Invoice Persistencia + InvoiceService

ReservationRoom no aparece en el diagrama porque UML expresa la asociaciΓ³n 1..* directamente entre Reserva y Habitacion. En un modelo relacional esa asociaciΓ³n necesita una tabla propia, y ademΓ‘s da un lugar natural donde congelar el precio por noche en el momento de reservar: si la tarifa cambia despuΓ©s, lo que el huΓ©sped aceptΓ³ pagar no cambia con ella.