Modelos de Datos
User Model
CustomUser Model
# managementApp/modelsfolder/UserModel.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
phone = models.CharField(max_length=20, blank=True, null=True)
document_id = models.CharField(max_length=50, blank=True, null=True)
address = models.TextField(blank=True, null=True)
birth_date = models.DateField(blank=True, null=True)
profile_image = models.URLField(blank=True, null=True)
is_verified = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.first_name} {self.last_name} ({self.email})"
Profile System
class Profile(models.Model):
profile_name = models.CharField(max_length=100, unique=True)
description = models.TextField(blank=True, null=True)
permissions = models.JSONField(default=dict)
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.profile_name
class UserProfile(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
assigned_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ('user', 'profile')
Campos Principales
- Identificación: username, email, document_id
- Información Personal: first_name, last_name, phone, address, birth_date
- Seguridad: password (hasheado con BCrypt), is_verified
- Metadatos: created_at, updated_at, profile_image
Property Model
PropertyModel
# managementApp/modelsfolder/PropertyModel.py
class PropertyModel(models.Model):
PROPERTY_TYPES = [
('house', 'Casa'),
('apartment', 'Apartamento'),
('commercial', 'Local Comercial'),
('office', 'Oficina'),
('warehouse', 'Bodega'),
]
STATUS_CHOICES = [
('available', 'Disponible'),
('occupied', 'Ocupada'),
('maintenance', 'Mantenimiento'),
('inactive', 'Inactiva'),
]
title = models.CharField(max_length=200)
description = models.TextField()
property_type = models.CharField(max_length=20, choices=PROPERTY_TYPES)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='available')
# Location
address = models.TextField()
city = models.CharField(max_length=100)
state = models.CharField(max_length=100)
country = models.CharField(max_length=100)
postal_code = models.CharField(max_length=20, blank=True)
latitude = models.DecimalField(max_digits=10, decimal_places=8, blank=True, null=True)
longitude = models.DecimalField(max_digits=11, decimal_places=8, blank=True, null=True)
# Characteristics
bedrooms = models.IntegerField(default=1)
bathrooms = models.IntegerField(default=1)
area = models.DecimalField(max_digits=8, decimal_places=2) # m²
floors = models.IntegerField(default=1)
parking_spaces = models.IntegerField(default=0)
balcony = models.BooleanField(default=False)
# Financial
price = models.DecimalField(max_digits=12, decimal_places=2)
currency = models.CharField(max_length=3, default='COP')
deposit = models.DecimalField(max_digits=12, decimal_places=2, default=0)
admin_fee = models.DecimalField(max_digits=10, decimal_places=2, default=0)
# Amenities
amenities = models.JSONField(default=list) # ['wifi', 'ac', 'gym', ...]
# Dates
available_from = models.DateField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.title} - {self.city}"
PropertyImage Model
class PropertyImage(models.Model):
property = models.ForeignKey(PropertyModel, on_delete=models.CASCADE, related_name='images')
image_url = models.URLField()
is_main = models.BooleanField(default=False)
order_index = models.IntegerField(default=0)
uploaded_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['order_index']
Características Principales
- Información Básica: title, description, property_type, status
- Ubicación: address, city, state, coordinates
- Características Físicas: bedrooms, bathrooms, area, floors
- Información Financiera: price, deposit, admin_fee
- Amenidades: JSON field para flexibilidad
- Imágenes: Relación uno a muchos con PropertyImage
Contract Model
ContractModel
# managementApp/modelsfolder/ContractModel.py
class ContractModel(models.Model):
CONTRACT_TYPES = [
('residential', 'Residencial'),
('commercial', 'Comercial'),
]
STATUS_CHOICES = [
('draft', 'Borrador'),
('pending', 'Pendiente'),
('active', 'Activo'),
('expired', 'Vencido'),
('terminated', 'Terminado'),
]
property = models.ForeignKey(PropertyModel, on_delete=models.CASCADE)
landlord = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='landlord_contracts')
tenant = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='tenant_contracts')
guarantor = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True, blank=True)
contract_type = models.CharField(max_length=20, choices=CONTRACT_TYPES)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
# Dates
start_date = models.DateField()
end_date = models.DateField()
signing_date = models.DateTimeField(blank=True, null=True)
# Financial Terms
monthly_rent = models.DecimalField(max_digits=12, decimal_places=2)
deposit = models.DecimalField(max_digits=12, decimal_places=2)
payment_day = models.IntegerField(default=1) # Day of month (1-31)
annual_increase = models.DecimalField(max_digits=5, decimal_places=2, default=0) # Percentage
# Contract Details
contract_text = models.TextField()
terms_and_conditions = models.TextField()
special_clauses = models.TextField(blank=True)
# Digital Signatures
landlord_signature_hash = models.CharField(max_length=255, blank=True)
tenant_signature_hash = models.CharField(max_length=255, blank=True)
guarantor_signature_hash = models.CharField(max_length=255, blank=True)
# Metadata
contract_hash = models.CharField(max_length=255, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"Contrato {self.id} - {self.property.title}"
ContractSignature Model
class ContractSignature(models.Model):
contract = models.ForeignKey(ContractModel, on_delete=models.CASCADE, related_name='signatures')
signer = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
signature_data = models.TextField() # Base64 signature image
signature_hash = models.CharField(max_length=255)
ip_address = models.GenericIPAddressField()
user_agent = models.TextField()
signed_at = models.DateTimeField(auto_now_add=True)
Características del Contrato
- Partes: property, landlord, tenant, guarantor (opcional)
- Términos Financieros: monthly_rent, deposit, payment_day
- Fechas: start_date, end_date, signing_date
- Contenido: contract_text, terms_and_conditions, special_clauses
- Firmas Digitales: Hashes de seguridad para cada firma
- Integridad: contract_hash para verificación de autenticidad
Menu Model
MenuModel
# managementApp/modelsfolder/MenuModel.py
class MenuModel(models.Model):
menu_title = models.CharField(max_length=100)
icon = models.CharField(max_length=50, blank=True, null=True)
path = models.CharField(max_length=200)
parent_menu = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True)
permissions_required = models.CharField(max_length=200, blank=True, null=True)
order_index = models.IntegerField(default=0)
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['order_index', 'menu_title']
def __str__(self):
return self.menu_title
Características del Menú
- Jerarquía: parent_menu para estructuras anidadas
- Permisos: permissions_required para control de acceso
- Orden: order_index para organización visual
- Metadatos: icon, path, active status
Subscription Model
SubscriptionModel
# managementApp/modelsfolder/SubscriptionModel.py
class SubscriptionModel(models.Model):
STATUS_CHOICES = [
('active', 'Activa'),
('inactive', 'Inactiva'),
('canceled', 'Cancelada'),
('expired', 'Expirada'),
]
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
stripe_subscription_id = models.CharField(max_length=200, unique=True)
stripe_customer_id = models.CharField(max_length=200)
status = models.CharField(max_length=20, choices=STATUS_CHOICES)
plan_name = models.CharField(max_length=100)
amount = models.DecimalField(max_digits=10, decimal_places=2)
currency = models.CharField(max_length=3, default='COP')
interval = models.CharField(max_length=20) # monthly, yearly
current_period_start = models.DateTimeField()
current_period_end = models.DateTimeField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.user.email} - {self.plan_name}"
Relaciones y Constraints
Relaciones Principales
# Usuario tiene múltiples propiedades (como propietario)
landlord_contracts = CustomUser.objects.filter(landlord_contracts__isnull=False)
# Usuario puede ser inquilino en múltiples contratos
tenant_contracts = CustomUser.objects.filter(tenant_contracts__isnull=False)
# Propiedad puede tener múltiples contratos en el tiempo
property_contracts = PropertyModel.objects.prefetch_related('contractmodel_set')
# Usuario puede tener múltiples perfiles/roles
user_profiles = CustomUser.objects.prefetch_related('userprofile_set__profile')
Constraints de Integridad
- Unique Constraints: email único, contract_hash único
- Foreign Key Constraints: Eliminación en cascada donde apropiado
- Check Constraints: Validaciones de rangos y valores permitidos
- Index Optimization: Índices en campos de búsqueda frecuente
Validaciones de Modelo
def clean(self):
# Validación personalizada en PropertyModel
if self.end_date <= self.start_date:
raise ValidationError('End date must be after start date')
if self.monthly_rent <= 0:
raise ValidationError('Monthly rent must be positive')