From 99426f7335ae7155ff7cc13208bddbfbc9d8b56b Mon Sep 17 00:00:00 2001 From: adrigongv23 Date: Fri, 6 Mar 2026 13:35:17 +0100 Subject: [PATCH] =?UTF-8?q?Reestructuraci=C3=B3n=20de=20la=20base=20de=20d?= =?UTF-8?q?atos,=20para=20una=20correlaci=C3=B3n=20con=20el=20diagrama=20d?= =?UTF-8?q?e=20clases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Plataform_Web/documentos/models.py | 43 ++++++---- Plataform_Web/gestion/models.py | 123 +++++++++++++++++++---------- Plataform_Web/pruebas/models.py | 53 ++++++++++--- Plataform_Web/temporadas/models.py | 11 ++- Plataform_Web/users/models.py | 6 +- 5 files changed, 168 insertions(+), 68 deletions(-) diff --git a/Plataform_Web/documentos/models.py b/Plataform_Web/documentos/models.py index 40f4821..a4370f8 100644 --- a/Plataform_Web/documentos/models.py +++ b/Plataform_Web/documentos/models.py @@ -9,7 +9,7 @@ class Documento(models.Model): ('aerodinamica', 'Aerodinámica'), ('chasis', 'Chasis'), ('business', 'Business & Operations'), - ('epowertrain', 'E-Powertrain'), + ('e_powertrain', 'E-Powertrain'), ('electronica', 'Electrónica'), ('sdf', 'SDF'), ('motor_transmision', 'Motor & Transmisión'), @@ -22,12 +22,14 @@ class Documento(models.Model): ('diseno', 'Diseño / CAD'), ('concepto', 'Concepto'), ('simulacion', 'Simulación'), - ('informe', 'Informe Técnico'), + ('dossier_patrocinado', 'Dossier Patrocinio'), + ('informe', 'Informe') ('tutorial', 'Tutorial'), ('otro', 'Otro'), ) - titulo = models.CharField(max_length=100, verbose_name="Título del documento") + # CAMPOS + nombre = models.CharField(max_length=100, verbose_name="Nombre del documento") # Aquí definimos dónde se guardan los archivos. # upload_to='ingenieria/' creará esa carpeta automáticamente. @@ -35,29 +37,40 @@ class Documento(models.Model): categoria = models.CharField(max_length=20, choices=CATEGORIAS) tipo = models.CharField(max_length=20, choices=TIPO_DOC, default='informe') + descripcion = models.TextField(blank=True, null=True, verbose_name="Descripción del documento") + fecha_subida = models.DateTimeField(auto_now_add=True) - descripcion = models.TextField(blank=True, null=True, verbose_name="Descripción o notas") + # RELACIONES + # Relacion Documento - Temporadas (Relación 1..* a 1..* ) + temporada = models.ManyToManyField(Temporada, related_name="documentos_asociados") - # RELACIONES (La parte potente) - # 1. Si borras una temporada, ¿borramos sus documentos? -> models.CASCADE (Sí) - temporada = models.ForeignKey(Temporada, on_delete=models.CASCADE) - - # 2. Si borras un usuario, ¿borramos sus docs? -> models.SET_NULL (No, mejor mantenemos el doc y ponemos usuario a null) + # 2. Relación 1 a N con el Usuario (Miembros) subido_por = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True, related_name="documentos_subidos") - fecha_subida = models.DateTimeField(auto_now_add=True) - class Meta: - verbose_name = "Documento Técnico" - verbose_name_plural = "Documentos de Ingeniería" + verbose_name = "Documento" + verbose_name_plural = "Documentos" ordering = ['-fecha_subida'] def __str__(self): - return f"{self.titulo} ({self.temporada})" + return f"{self.nombre} ({self.temporada})" def delete(self, *args, **kwargs): # Esto borra el archivo físico del disco duro cuando borras la entrada en la base de datos if self.archivo: if os.path.isfile(self.archivo.path): os.remove(self.archivo.path) - super().delete(*args, **kwargs) \ No newline at end of file + super().delete(*args, **kwargs) + +# Herencia de la clase Factura: Factura hereda de Documento +class Factura(Documento): + # Al heredar de Documento, ya tiene nombre, archivo, categoria, etc. + empresa = models.CharField(max_length=100, verbose_name="Nombre de la empresa") + importe = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Importe (€)") + + class Meta: + verbose_name = "Factura" + verbose_name_plural = "Facturas" + + def __str__(self): + return f"Factura {self.empresa} - {self.importe}€" \ No newline at end of file diff --git a/Plataform_Web/gestion/models.py b/Plataform_Web/gestion/models.py index 4226dee..5155e65 100644 --- a/Plataform_Web/gestion/models.py +++ b/Plataform_Web/gestion/models.py @@ -1,37 +1,9 @@ from django.db import models -from users.models import CustomUser +from django.conf import settings from temporadas.models import Temporada -class Factura(models.Model): - CATEGORIAS = ( - ('aerodinamica', 'Aerodinámica'), - ('chasis', 'Chasis'), - ('business', 'Business & Operations'), - ('epowertrain', 'E-Powertrain'), - ('electronica', 'Electrónica'), - ('sdf', 'SDF'), - ('motor_transmision', 'Motor & Transmisión'), - ('software', 'Software'), - ('general', 'General'), - ) - nombre = models.CharField(max_length=100, verbose_name="Concepto de la factura") - empresa = models.CharField(max_length=100, verbose_name="Empresa / Proveedor") - categoria = models.CharField(max_length=30, choices=CATEGORIAS) - descripcion = models.TextField(blank=True, null=True, verbose_name="Breve descripción") - - # Puede ser foto (jpg/png) o pdf - archivo = models.FileField(upload_to='facturas/', verbose_name="Foto o PDF de la factura") - - # Relaciones - subido_por = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True, verbose_name="Usuario que la sube") - temporada = models.ForeignKey(Temporada, on_delete=models.CASCADE) - - def __str__(self): - return f"{self.nombre} - {self.empresa}" - - -class Patrocinador(models.Model): +class Patrocinio(models.Model): TIPO_PATROCINIO = ( ('economico', 'Económico'), @@ -40,24 +12,95 @@ class Patrocinador(models.Model): ) ESTADOS = ( - ('en contacto', 'En contacto'), + ('en_contacto', 'En contacto'), ('aceptado', 'Aceptado / Activo'), ('denegado', 'Denegado'), ) empresa = models.CharField(max_length=100, verbose_name="Nombre de la Empresa") + persona_contacto = models.CharField(max_length=100, blank=True, null=True, verbose_name="Persona de contacto") email_contacto = models.EmailField(verbose_name="Correo de contacto") - tipo_empresa = models.CharField(max_length=30, verbose_name="Tipo de la Empresa") tipo_patrocinio = models.CharField(max_length=30, choices=TIPO_PATROCINIO) + estado = models.CharField(max_length=20, choices=ESTADOS, default='en_contacto') - # Solo se rellena si el patrocinio es de piezas - detalle_piezas = models.TextField(blank=True, null=True, help_text="Detallar qué se ofrece si es en especies") - - estado = models.CharField(max_length=20, choices=ESTADOS, default='contacto') - + # Si el patrocinio es únicamente material, se quedará en 0. Si es económico o mixto, se rellena + importe_economico = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name="Importe (€)") + + fecha_contacto = models.DateField(auto_now_add=True) # Relaciones - contacto_equipo = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True, verbose_name="Miembro al cargo") - temporada = models.ForeignKey(Temporada, on_delete=models.CASCADE) + temporada = models.ForeignKey(Temporada, on_delete=models.CASCADE, related_name="patrocinios", verbose_name="Temporada del patrocinio") + contacto_equipo = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name="patrocinios_gestionados", verbose_name="Persona al cargo del Patrocinio") + + class Meta: + verbose_name = "Patrocinio" + verbose_name_plural = "Patrocinios" def __str__(self): - return self.empresa \ No newline at end of file + return f"{self.empresa} ({self.get_estado_display()})" + +class Pieza(models.Model): + nombre = models.CharField(max_length=100, verbose_name="Nombre de la pieza/material") + cantidad = models.PositiveIntegerField(default=1) + + # COMPOSICIÓN ESTRICTA: Si se borra el Patrocinio, se borran las Piezas irremediablemente (CASCADE) + patrocinio = models.ForeignKey(Patrocinio, on_delete=models.CASCADE, related_name="piezas") + + def __str__(self): + return f"{self.cantidad}x {self.nombre} (De: {self.patrocinio.empresa})" + +class Contabilidad(models.Model): + concepto = models.CharField(max_length=150) + importe = models.DecimalField(max_digits=10, decimal_places=2) + fecha = models.DateField() + observaciones = models.TextField(blank=True, null=True) + + # Relación con Temporada. PROTECT evita que borres una temporada si tiene contabilidad asociada (por temas legales) + temporada = models.ForeignKey(Temporada, on_delete=models.PROTECT, related_name="%(class)s_registrados") + + class Meta: + abstract = True #marcamos la clase como abstracta + ordering = ['-fecha'] + + +class Gasto(Contabilidad): + + CATEGORIAS_GASTOS = ( + ('aerodinamica', 'Aerodinámica'), + ('chasis', 'Chasis'), + ('business', 'Business & Operations'), + ('e_powertrain', 'E-Powertrain'), + ('electronica', 'Electrónica'), + ('sdf', 'SDF'), + ('motor_transmision', 'Motor & Transmisión'), + ('software', 'Software'), + ('general', 'General'), + ) + # Hereda concepto, importe, fecha, observaciones y temporada automáticamente + categoria = models.CharField(max_length=30, choices=CATEGORIAS_GASTOS, verbose_name="Área del gasto") + doc_justificativo = models.FileField(upload_to='contabilidad/gastos/', blank=True, null=True, verbose_name="Ticket o Factura (PDF/IMG)") + + class Meta: + verbose_name = "Gasto" + verbose_name_plural = "Gastos" + + def __str__(self): + return f"[-{self.importe}€] {self.concepto} ({self.temporada})" + +class Ingreso(Contabilidad): + CATEGORIAS_INGRESO = ( + ('patrocinador', 'Patrocinador'), + ('donación', 'Donación'), + ('premio', 'Premio'), + ('recaudación', 'Recaudación'), + ('Cuotas', 'Cuotas'), + ('Ventas', 'ventas'), + ) + + # Hereda concepto, importe, fecha, observaciones y temporada automáticamente + categoria = models.CharField(max_length=30, choices=CATEGORIAS_INGRESO, verbose_name="Categoria del Ingreso") + class Meta: + verbose_name = "Ingreso" + verbose_name_plural = "Ingresos" + + def __str__(self): + return f"[+{self.importe}€] {self.concepto} ({self.temporada})" diff --git a/Plataform_Web/pruebas/models.py b/Plataform_Web/pruebas/models.py index d0ce314..0f8ac45 100644 --- a/Plataform_Web/pruebas/models.py +++ b/Plataform_Web/pruebas/models.py @@ -1,35 +1,70 @@ from django.db import models +from django.conf import settings from temporadas.models import Temporada -class TestGeneral(models.Model): +class Prueba(models.Model): CATEGORIAS_TEST = ( ('aerodinamica', 'Aerodinámica'), ('chasis', 'Chasis'), - ('business', 'Business & Operations'), ('epowertrain', 'E-Powertrain'), ('electronica', 'Electrónica'), ('sdf', 'SDF'), ('motor_transmision', 'Motor & Transmisión'), + ('software', 'Software'), ('general', 'General'), ) nombre = models.CharField(max_length=150, verbose_name="Nombre del Test") - descripcion = models.TextField(verbose_name="Objetivo / Descripción del test") + descripcion = models.TextField(verbose_name="Objetivo / Descripción de la prueba") fecha_inicio = models.DateField() fecha_fin = models.DateField() categoria = models.CharField(max_length=30, choices=CATEGORIAS_TEST, default='general') - resultados = models.TextField(blank=True, null=True, verbose_name="Conclusiones y Resultados") - # Relación - temporada = models.ForeignKey(Temporada, on_delete=models.CASCADE) + # Relaciones + # Relacion con Temporada + temporada = models.ForeignKey(Temporada, on_delete=models.CASCADE, related_name="pruebas") + #Relacion con Usuario + realizado_por = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name="pruebas_realizadas", verbose_name="Realizado por") class Meta: - verbose_name = "Test General" - verbose_name_plural = "Tests Generales" + verbose_name = "Prueba" + verbose_name_plural = "Pruebas" ordering = ['-fecha_inicio'] def __str__(self): - return f"{self.nombre} ({self.fecha_inicio})" \ No newline at end of file + return f"{self.nombre} ({self.fecha_inicio})" + +class Telemetria(models.Model): + nombre = models.CharField(max_length=100, verbose_name="Nombre del registro") + archivo_csv = models.FileField(upload_to='telemetria/archivos_csv/', verbose_name="Archivo de Datos (CSV)") + fecha_subida = models.DateTimeField(auto_now_add=True) + + # La telemetría se obtiene durante una Prueba en pista + prueba = models.ForeignKey(Prueba, on_delete=models.CASCADE, related_name="archivos_telemetria") + + class Meta: + verbose_name = "Registro de Telemetría" + verbose_name_plural = "Registros de Telemetría" + + def __str__(self): + return f"Telemetría: {self.nombre} (De: {self.prueba.nombre})" + + +class Variable(models.Model): + nombre = models.CharField(max_length=50, verbose_name="Nombre ") + unidad_medida = models.CharField(max_length=20, verbose_name="Unidad de medida", blank=True, null=True) + descripcion = models.TextField(blank=True, null=True) + + # COMPOSICIÓN + telemetria = models.ForeignKey(Telemetria, on_delete=models.CASCADE, related_name="variables") + + class Meta: + verbose_name = "Variable de Telemetría" + verbose_name_plural = "Variables de Telemetría" + + def __str__(self): + unidad = f" [{self.unidad_medida}]" if self.unidad_medida else "" + return f"{self.nombre}{unidad} (De: {self.telemetria.nombre})" \ No newline at end of file diff --git a/Plataform_Web/temporadas/models.py b/Plataform_Web/temporadas/models.py index 918a948..7d0e19c 100644 --- a/Plataform_Web/temporadas/models.py +++ b/Plataform_Web/temporadas/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.conf import settings class Temporada(models.Model): # El nombre será algo como "Gades 2024-25" @@ -13,8 +14,16 @@ class Temporada(models.Model): # Checkbox para marcar cuál es la temporada que estamos viviendo ahora actual = models.BooleanField(default=False, verbose_name="¿Es la temporada actual?") + # Relaciones + # Relacion N a N con miembros + miembros = models.ManyToManyField( + settings.AUTH_USER_MODEL, + related_name="temporadas_participadas", + blank=True, + verbose_name="Miembros del equipo" + ) + class Meta: - # Esto es para que en el panel salga "Temporadas" y no "Temporadas" verbose_name = "Temporada" verbose_name_plural = "Temporadas" ordering = ['-fecha_inicio'] # Ordena las más nuevas primero diff --git a/Plataform_Web/users/models.py b/Plataform_Web/users/models.py index af3c0f0..2661b57 100644 --- a/Plataform_Web/users/models.py +++ b/Plataform_Web/users/models.py @@ -6,13 +6,13 @@ class CustomUser(AbstractUser): ROL_CHOICES = ( ('directiva', 'Directiva'), ('jefe_area', 'Jefe de Área'), - ('empleado', 'Empleado'), + ('miembro', 'Miembro'), ) ESPECIALIDAD_CHOICES = ( ('aerodinamica', 'Aerodinámica'), ('chasis', 'Chasis'), - ('business', 'Business & Operations'), + ('business_operations', 'Business & Operations'), ('epowertrain', 'E-Powertrain'), ('electronica', 'Electrónica'), ('sdf', 'SDF'), @@ -25,7 +25,7 @@ class CustomUser(AbstractUser): rol = models.CharField( max_length=20, choices=ROL_CHOICES, - default='empleado', + default='miembro', verbose_name="Rol en el equipo" )