diff --git a/Escritorio_Boxes/diseño.py b/Escritorio_Boxes/diseño.py index 094772d..9d97477 100644 --- a/Escritorio_Boxes/diseño.py +++ b/Escritorio_Boxes/diseño.py @@ -2,34 +2,28 @@ import socket import time import math import random -from datetime import datetime # <--- IMPORTANTE: Nueva librería -UDP_IP = "127.0.0.1" +# --- CONFIGURACIÓN --- +UDP_IP = "127.0.0.1" UDP_PORT = 4210 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -print("TELEMETRÍA SIMULADA - ENVÍO DE DATOS") -print(" Formato: ECT | RPM | BATT | SPEED | HH:MM:SS") +print(f"--- SIMULADOR DE COCHE G26 ---") +print(f"Enviando datos falsos a {UDP_IP}:{UDP_PORT}") t = 0 while True: - # Generación de datos (Igual que antes) - ect = 85 + (5 * math.sin(t / 5.0)) + random.uniform(-0.2, 0.2) - rpm = 3000 + (1000 * math.sin(t / 2.0)) - batt = 13.8 + random.uniform(-0.1, 0.1) - speed = 90 + (30 * math.sin(t / 3.0)) + # Generar una temperatura que sube y baja mucho para probar todos los colores + # Oscilará entre 40ºC y 120ºC + ect = 80 + (40 * math.sin(t / 10.0)) + random.uniform(-0.5, 0.5) - # --- CAMBIO AQUÍ: OBTENER HORA REAL --- - # Obtenemos hora actual y la convertimos a texto - hora_real = datetime.now().strftime("%H:%M:%S.%f")[:-3] # Hora con milisegundos - - # Mensaje con la hora legible al final - mensaje = f"{ect:.2f}|{int(rpm)}|{batt:.2f}|{int(speed)}|{hora_real}" + # Enviar solo el número + mensaje = f"{ect:.2f}" sock.sendto(mensaje.encode(), (UDP_IP, UDP_PORT)) - print(f"TX: {mensaje}") + print(f"Simulando: {mensaje} °C") t += 0.1 - time.sleep(0.002) \ No newline at end of file + time.sleep(0.05) \ No newline at end of file diff --git a/Escritorio_Boxes/monitor.py b/Escritorio_Boxes/monitor.py index b78278d..920d7c6 100644 --- a/Escritorio_Boxes/monitor.py +++ b/Escritorio_Boxes/monitor.py @@ -2,91 +2,90 @@ import socket import matplotlib.pyplot as plt import matplotlib.animation as animation from collections import deque +from datetime import datetime # --- CONFIGURACIÓN --- -UDP_IP = "0.0.0.0" -UDP_PORT = 4210 -MAX_PUNTOS = 150 +UDP_IP = "0.0.0.0" # Escuchamos en Todas las interfaces posibles (WiFi, Ethernet...) +UDP_PORT = 4210 # Mismo puerto que usamos para la ESP32 +MAX_PUNTOS = 200 # Vamos a mostrar en la gráfica los últimos 200 datos +# Cola de datos data_ect = deque([0]*MAX_PUNTOS, maxlen=MAX_PUNTOS) -data_rpm = deque([0]*MAX_PUNTOS, maxlen=MAX_PUNTOS) -data_bat = deque([0]*MAX_PUNTOS, maxlen=MAX_PUNTOS) -data_spd = deque([0]*MAX_PUNTOS, maxlen=MAX_PUNTOS) +#Ocultamos estos valores ya que por el momento únicamente vamos a trabajar con la temperatura +#data_rpm = deque([0]*MAX_PUNTOS, maxlen=MAX_PUNTOS) +#data_bat = deque([0]*MAX_PUNTOS, maxlen=MAX_PUNTOS) + +# Configuración del socket UDP sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((UDP_IP, UDP_PORT)) sock.setblocking(False) # --- DISEÑO GRÁFICO --- plt.style.use('dark_background') -fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, sharex=True, figsize=(8, 10)) +fig, ax1 = plt.subplots(figsize=(10, 6)) # Usaremos una única ventana grande # Título principal + RELOJ +fig.canvas.manager.set_window_title('G26 Telemetry - Monitor de Temperatura') fig.suptitle('GADES TELEMETRY SYSTEM', fontsize=16, fontweight='bold', color='white') -# Creamos un texto vacío arriba a la derecha para la hora -texto_reloj = fig.text(0.85, 0.97, "--:--:--", fontsize=12, color='cyan', ha='center', fontweight='bold') -def setup_plot(ax, color, label, y_min, y_max): - line, = ax.plot([], [], color=color, lw=2) - ax.set_ylabel(label) - ax.set_ylim(y_min, y_max) - ax.grid(True, alpha=0.3, linestyle='--') - props = dict(boxstyle='round', facecolor='black', alpha=0.7, edgecolor=color) - text = ax.text(0.03, 0.85, '', transform=ax.transAxes, - fontsize=14, color=color, fontweight='bold', bbox=props) - return line, text +# Reloj en la esquina superior derecha +txt_reloj = fig.text(0.85, 0.95, '--:--:--', fontsize=12, color='white', fontweight='bold') -line_ect, txt_ect = setup_plot(ax1, '#ff3333', 'ECT (°C)', 50, 110) -line_rpm, txt_rpm = setup_plot(ax2, '#ffff33', 'RPM', 0, 5000) -line_bat, txt_bat = setup_plot(ax3, '#33ffff', 'BATT (V)', 12, 15) -line_spd, txt_spd = setup_plot(ax4, '#33ff33', 'SPEED', 0, 140) +# Configuración de la línea (empieza en blanco pero cambiará) +line_ect, = ax1.plot([], [], color='white', lw=3) +ax1.set_ylabel('Temperatura (°C)', fontsize=14) +ax1.set_ylim(0, 130) # Rango de temperatura (0 a 130 grados) +ax1.grid(True, alpha=0.3, linestyle='--') + +# Caja de texto con el valor actual +props = dict(boxstyle='round', facecolor='black', alpha=0.8, edgecolor='white') +txt_ect = ax1.text(0.02, 0.90, 'ESPERANDO...', transform=ax1.transAxes, + fontsize=16, color='white', fontweight='bold', bbox=props) + +def get_color(temp): + if temp > 105: + return '#ff3333' # ROJO (Peligro) + elif temp >= 95: + return '#ffff33' # AMARILLO (Precaución) + elif temp >= 65: + return '#33ff33' # VERDE (Ok) + else: + return '#33ffff' # AZUL (Frío) def update(frame): + # 1. Actualizar el Reloj con la hora del PC + ahora = datetime.now().strftime("%H:%M:%S") + txt_reloj.set_text(f"HORA: {ahora}") try: while True: data, addr = sock.recvfrom(1024) msg = data.decode('utf-8') - partes = msg.split('|') + # partes = msg.split('|') - # Extraer valores numéricos - val_ect = float(partes[0]) - val_rpm = float(partes[1]) - val_bat = float(partes[2]) - val_spd = float(partes[3]) + try: + val_ect = float(msg) # Convertimos texto a número + + data_ect.append(val_ect) # Guardamos el dato + color_actual = get_color(val_ect) # Para que el valor cambie de color segun la temepratura + + txt_ect.set_text(f"TEMP: {val_ect: .1f} °C") + txt_ect.set_color(color_actual) - # --- EXTRAER LA HORA (Es el elemento 4) --- - val_time = partes[4] - - # Guardar datos - data_ect.append(val_ect) - data_rpm.append(val_rpm) - data_bat.append(val_bat) - data_spd.append(val_spd) - - # Actualizar textos gráficas - txt_ect.set_text(f"TEMP: {val_ect:.1f} °C") - txt_rpm.set_text(f"RPM: {int(val_rpm)}") - txt_bat.set_text(f"BATT: {val_bat:.2f} V") - txt_spd.set_text(f"VEL: {int(val_spd)} Km/h") - - # --- ACTUALIZAR EL RELOJ DE ARRIBA --- - texto_reloj.set_text(f"{val_time}") + except ValueError: + print(f"Error de formato: {msg}") except BlockingIOError: pass except Exception as e: print(f"Error: {e}") + # Actualizamos la línea gráfica x_range = range(len(data_ect)) line_ect.set_data(x_range, data_ect) - line_rpm.set_data(x_range, data_rpm) - line_bat.set_data(x_range, data_bat) - line_spd.set_data(x_range, data_spd) + ax1.set_xlim(0, max(len(data_ect), 1)) - ax1.set_xlim(0, len(data_ect)) - - return line_ect, line_rpm, line_bat, line_spd, txt_ect, txt_rpm, txt_bat, txt_spd, texto_reloj + return line_ect, txt_ect, txt_reloj -plt.tight_layout(rect=[0, 0, 1, 0.96]) # Dejar hueco arriba para el reloj ani = animation.FuncAnimation(fig, update, interval=20, blit=False, cache_frame_data=False) plt.show() \ No newline at end of file diff --git a/Plataform_Web/documentos/__init__.py b/Plataform_Web/documentos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Plataform_Web/documentos/__pycache__/__init__.cpython-314.pyc b/Plataform_Web/documentos/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..b85ee5f Binary files /dev/null and b/Plataform_Web/documentos/__pycache__/__init__.cpython-314.pyc differ diff --git a/Plataform_Web/documentos/__pycache__/admin.cpython-314.pyc b/Plataform_Web/documentos/__pycache__/admin.cpython-314.pyc new file mode 100644 index 0000000..c50c4ac Binary files /dev/null and b/Plataform_Web/documentos/__pycache__/admin.cpython-314.pyc differ diff --git a/Plataform_Web/documentos/__pycache__/apps.cpython-314.pyc b/Plataform_Web/documentos/__pycache__/apps.cpython-314.pyc new file mode 100644 index 0000000..de968a1 Binary files /dev/null and b/Plataform_Web/documentos/__pycache__/apps.cpython-314.pyc differ diff --git a/Plataform_Web/documentos/__pycache__/models.cpython-314.pyc b/Plataform_Web/documentos/__pycache__/models.cpython-314.pyc new file mode 100644 index 0000000..e77b30c Binary files /dev/null and b/Plataform_Web/documentos/__pycache__/models.cpython-314.pyc differ diff --git a/Plataform_Web/documentos/admin.py b/Plataform_Web/documentos/admin.py new file mode 100644 index 0000000..97dc990 --- /dev/null +++ b/Plataform_Web/documentos/admin.py @@ -0,0 +1,14 @@ +from django.contrib import admin +from .models import Documento + +@admin.register(Documento) +class DocumentoAdmin(admin.ModelAdmin): + list_display = ('titulo', 'categoria', 'tipo', 'temporada', 'subido_por', 'fecha_subida') + list_filter = ('temporada', 'categoria', 'tipo') # ¡Filtros laterales muy útiles! + search_fields = ('titulo', 'descripcion') + + # Esto hace que el campo "subido_por" se rellene solo con tu usuario al crear un doc + def save_model(self, request, obj, form, change): + if not obj.subido_por: + obj.subido_por = request.user + super().save_model(request, obj, form, change) \ No newline at end of file diff --git a/Plataform_Web/documentos/apps.py b/Plataform_Web/documentos/apps.py new file mode 100644 index 0000000..b42b59d --- /dev/null +++ b/Plataform_Web/documentos/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class DocumentosConfig(AppConfig): + name = 'documentos' diff --git a/Plataform_Web/documentos/migrations/0001_initial.py b/Plataform_Web/documentos/migrations/0001_initial.py new file mode 100644 index 0000000..3598421 --- /dev/null +++ b/Plataform_Web/documentos/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# Generated by Django 6.0.2 on 2026-02-17 11:36 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('temporadas', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Documento', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('titulo', models.CharField(max_length=100, verbose_name='Título del documento')), + ('archivo', models.FileField(upload_to='ingenieria_docs/')), + ('categoria', models.CharField(choices=[('aerodinamica', 'Aerodinámica'), ('chasis', 'Chasis'), ('suspension', 'Suspensión'), ('motor', 'Motor/Powertrain'), ('electronica', 'Electrónica'), ('general', 'General / Normativa')], max_length=20)), + ('tipo', models.CharField(choices=[('diseno', 'Diseño / CAD'), ('simulacion', 'Simulación'), ('informe', 'Informe Técnico'), ('factura', 'Factura / Presupuesto'), ('otro', 'Otro')], default='informe', max_length=20)), + ('descripcion', models.TextField(blank=True, null=True, verbose_name='Descripción o notas')), + ('fecha_subida', models.DateTimeField(auto_now_add=True)), + ('subido_por', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='documentos_subidos', to=settings.AUTH_USER_MODEL)), + ('temporada', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='temporadas.temporada')), + ], + options={ + 'verbose_name': 'Documento Técnico', + 'verbose_name_plural': 'Documentos de Ingeniería', + 'ordering': ['-fecha_subida'], + }, + ), + ] diff --git a/Plataform_Web/documentos/migrations/__init__.py b/Plataform_Web/documentos/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Plataform_Web/documentos/migrations/__pycache__/0001_initial.cpython-314.pyc b/Plataform_Web/documentos/migrations/__pycache__/0001_initial.cpython-314.pyc new file mode 100644 index 0000000..4266495 Binary files /dev/null and b/Plataform_Web/documentos/migrations/__pycache__/0001_initial.cpython-314.pyc differ diff --git a/Plataform_Web/documentos/migrations/__pycache__/__init__.cpython-314.pyc b/Plataform_Web/documentos/migrations/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..f42726e Binary files /dev/null and b/Plataform_Web/documentos/migrations/__pycache__/__init__.cpython-314.pyc differ diff --git a/Plataform_Web/documentos/models.py b/Plataform_Web/documentos/models.py new file mode 100644 index 0000000..623a8d1 --- /dev/null +++ b/Plataform_Web/documentos/models.py @@ -0,0 +1,59 @@ +from django.db import models +from users.models import CustomUser +from temporadas.models import Temporada +import os + +class Documento(models.Model): + # Opciones de categorías (puedes añadir más) + CATEGORIAS = ( + ('aerodinamica', 'Aerodinámica'), + ('chasis', 'Chasis'), + ('suspension', 'Suspensión'), + ('motor', 'Motor/Powertrain'), + ('electronica', 'Electrónica'), + ('general', 'General / Normativa'), + ('software', 'Software') + ) + + TIPO_DOC = ( + ('diseno', 'Diseño / CAD'), + ('simulacion', 'Simulación'), + ('informe', 'Informe Técnico'), + ('factura', 'Factura / Presupuesto'), + ('otro', 'Otro'), + ) + + titulo = models.CharField(max_length=100, verbose_name="Título del documento") + + # Aquí definimos dónde se guardan los archivos. + # upload_to='ingenieria/' creará esa carpeta automáticamente. + archivo = models.FileField(upload_to='ingenieria_docs/') + + 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 o notas") + + # 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) + 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" + ordering = ['-fecha_subida'] + + def __str__(self): + return f"{self.titulo} ({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 diff --git a/Plataform_Web/documentos/tests.py b/Plataform_Web/documentos/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/Plataform_Web/documentos/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Plataform_Web/documentos/views.py b/Plataform_Web/documentos/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/Plataform_Web/documentos/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/Plataform_Web/gades_manager/__init__.py b/Plataform_Web/gades_manager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Plataform_Web/gades_manager/__pycache__/__init__.cpython-314.pyc b/Plataform_Web/gades_manager/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..df73379 Binary files /dev/null and b/Plataform_Web/gades_manager/__pycache__/__init__.cpython-314.pyc differ diff --git a/Plataform_Web/gades_manager/__pycache__/settings.cpython-314.pyc b/Plataform_Web/gades_manager/__pycache__/settings.cpython-314.pyc new file mode 100644 index 0000000..26ce28c Binary files /dev/null and b/Plataform_Web/gades_manager/__pycache__/settings.cpython-314.pyc differ diff --git a/Plataform_Web/gades_manager/__pycache__/urls.cpython-314.pyc b/Plataform_Web/gades_manager/__pycache__/urls.cpython-314.pyc new file mode 100644 index 0000000..8c5651d Binary files /dev/null and b/Plataform_Web/gades_manager/__pycache__/urls.cpython-314.pyc differ diff --git a/Plataform_Web/gades_manager/__pycache__/wsgi.cpython-314.pyc b/Plataform_Web/gades_manager/__pycache__/wsgi.cpython-314.pyc new file mode 100644 index 0000000..ed46cb3 Binary files /dev/null and b/Plataform_Web/gades_manager/__pycache__/wsgi.cpython-314.pyc differ diff --git a/Plataform_Web/gades_manager/asgi.py b/Plataform_Web/gades_manager/asgi.py new file mode 100644 index 0000000..153ca09 --- /dev/null +++ b/Plataform_Web/gades_manager/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for gades_manager project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gades_manager.settings') + +application = get_asgi_application() diff --git a/Plataform_Web/gades_manager/settings.py b/Plataform_Web/gades_manager/settings.py new file mode 100644 index 0000000..ac5e8e2 --- /dev/null +++ b/Plataform_Web/gades_manager/settings.py @@ -0,0 +1,130 @@ +""" +Django settings for gades_manager project. + +Generated by 'django-admin startproject' using Django 6.0.2. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/6.0/ref/settings/ +""" + +from pathlib import Path +import os + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-mwlyo^0ax9u9ts5(hw(e&vlgmr$nbla-3az0d%s(lymmp5^)$-' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'users', + 'temporadas', + 'documentos', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'gades_manager.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'gades_manager.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/6.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'formula_gades_bd', # El nombre exacto que pusiste en Workbench + 'USER': 'root', + 'PASSWORD': 'admin1234', # ¡La que apuntaste en el papel! + 'HOST': 'localhost', # Significa "este ordenador" + 'PORT': '3306', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/6.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/6.0/howto/static-files/ + +STATIC_URL = 'static/' + +AUTH_USER_MODEL = 'users.CustomUser' + +MEDIA_URL = '/media/' # La URL pública (ej: tudominio.com/media/archivo.pdf) +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # La carpeta física en tu ordenador diff --git a/Plataform_Web/gades_manager/urls.py b/Plataform_Web/gades_manager/urls.py new file mode 100644 index 0000000..f33adf1 --- /dev/null +++ b/Plataform_Web/gades_manager/urls.py @@ -0,0 +1,35 @@ +""" +URL configuration for gades_manager project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/6.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" + +from django.contrib import admin +from django.urls import path +from django.conf import settings +from django.conf.urls.static import static + +# 1. IMPORTA TU VISTA NUEVA +from gestion import views as gestion_views + +urlpatterns = [ + path('admin/', admin.site.urls), + + # 2. AÑADE ESTA RUTA (La cadena vacía '' significa "la página principal") + path('', gestion_views.home, name='home'), +] + +# Configuración de archivos (esto déjalo como estaba) +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/Plataform_Web/gades_manager/wsgi.py b/Plataform_Web/gades_manager/wsgi.py new file mode 100644 index 0000000..ef3583b --- /dev/null +++ b/Plataform_Web/gades_manager/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for gades_manager project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gades_manager.settings') + +application = get_wsgi_application() diff --git a/Plataform_Web/gestion/__init__.py b/Plataform_Web/gestion/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Plataform_Web/gestion/__pycache__/__init__.cpython-314.pyc b/Plataform_Web/gestion/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..de99138 Binary files /dev/null and b/Plataform_Web/gestion/__pycache__/__init__.cpython-314.pyc differ diff --git a/Plataform_Web/gestion/__pycache__/views.cpython-314.pyc b/Plataform_Web/gestion/__pycache__/views.cpython-314.pyc new file mode 100644 index 0000000..0d7b789 Binary files /dev/null and b/Plataform_Web/gestion/__pycache__/views.cpython-314.pyc differ diff --git a/Plataform_Web/gestion/admin.py b/Plataform_Web/gestion/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/Plataform_Web/gestion/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Plataform_Web/gestion/apps.py b/Plataform_Web/gestion/apps.py new file mode 100644 index 0000000..48586ea --- /dev/null +++ b/Plataform_Web/gestion/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class GestionConfig(AppConfig): + name = 'gestion' diff --git a/Plataform_Web/gestion/migrations/__init__.py b/Plataform_Web/gestion/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Plataform_Web/gestion/models.py b/Plataform_Web/gestion/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/Plataform_Web/gestion/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/Plataform_Web/gestion/tests.py b/Plataform_Web/gestion/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/Plataform_Web/gestion/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Plataform_Web/gestion/views.py b/Plataform_Web/gestion/views.py new file mode 100644 index 0000000..021ad80 --- /dev/null +++ b/Plataform_Web/gestion/views.py @@ -0,0 +1,6 @@ +from django.shortcuts import render +from django.http import HttpResponse + +def home(request): + # Esta función es la que decide qué mostrar cuando alguien entra a la web + return HttpResponse("

¡Bienvenido a la Plataforma de Gades! 🏎️💨

Sistema de Telemetría y Gestión v1.0

") \ No newline at end of file diff --git a/Plataform_Web/manage.py b/Plataform_Web/manage.py new file mode 100644 index 0000000..d8daff8 --- /dev/null +++ b/Plataform_Web/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gades_manager.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/Plataform_Web/media/ingenieria_docs/PruebaTest1.png b/Plataform_Web/media/ingenieria_docs/PruebaTest1.png new file mode 100644 index 0000000..0c824e6 Binary files /dev/null and b/Plataform_Web/media/ingenieria_docs/PruebaTest1.png differ diff --git a/Plataform_Web/temporadas/__init__.py b/Plataform_Web/temporadas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Plataform_Web/temporadas/__pycache__/__init__.cpython-314.pyc b/Plataform_Web/temporadas/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..73d6bbc Binary files /dev/null and b/Plataform_Web/temporadas/__pycache__/__init__.cpython-314.pyc differ diff --git a/Plataform_Web/temporadas/__pycache__/admin.cpython-314.pyc b/Plataform_Web/temporadas/__pycache__/admin.cpython-314.pyc new file mode 100644 index 0000000..c57527b Binary files /dev/null and b/Plataform_Web/temporadas/__pycache__/admin.cpython-314.pyc differ diff --git a/Plataform_Web/temporadas/__pycache__/apps.cpython-314.pyc b/Plataform_Web/temporadas/__pycache__/apps.cpython-314.pyc new file mode 100644 index 0000000..be03dbf Binary files /dev/null and b/Plataform_Web/temporadas/__pycache__/apps.cpython-314.pyc differ diff --git a/Plataform_Web/temporadas/__pycache__/models.cpython-314.pyc b/Plataform_Web/temporadas/__pycache__/models.cpython-314.pyc new file mode 100644 index 0000000..3413456 Binary files /dev/null and b/Plataform_Web/temporadas/__pycache__/models.cpython-314.pyc differ diff --git a/Plataform_Web/temporadas/admin.py b/Plataform_Web/temporadas/admin.py new file mode 100644 index 0000000..8345ef7 --- /dev/null +++ b/Plataform_Web/temporadas/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin +from .models import Temporada + +@admin.register(Temporada) +class TemporadaAdmin(admin.ModelAdmin): + # Esto define qué columnas se ven en la lista + list_display = ('nombre', 'fecha_inicio', 'fecha_fin', 'presupuesto', 'actual') + + # Esto añade un filtro a la derecha para ver rápido cuál es la actual + list_filter = ('actual',) + + # Esto añade una barra de búsqueda por nombre + search_fields = ('nombre',) \ No newline at end of file diff --git a/Plataform_Web/temporadas/apps.py b/Plataform_Web/temporadas/apps.py new file mode 100644 index 0000000..1a7e5b5 --- /dev/null +++ b/Plataform_Web/temporadas/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class TemporadasConfig(AppConfig): + name = 'temporadas' diff --git a/Plataform_Web/temporadas/migrations/0001_initial.py b/Plataform_Web/temporadas/migrations/0001_initial.py new file mode 100644 index 0000000..ba154fb --- /dev/null +++ b/Plataform_Web/temporadas/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 6.0.2 on 2026-02-17 11:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Temporada', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nombre', models.CharField(max_length=50, unique=True)), + ('fecha_inicio', models.DateField()), + ('fecha_fin', models.DateField()), + ('presupuesto', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)), + ('actual', models.BooleanField(default=False, verbose_name='¿Es la temporada actual?')), + ], + options={ + 'verbose_name': 'Temporada', + 'verbose_name_plural': 'Temporadas', + 'ordering': ['-fecha_inicio'], + }, + ), + ] diff --git a/Plataform_Web/temporadas/migrations/__init__.py b/Plataform_Web/temporadas/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Plataform_Web/temporadas/migrations/__pycache__/0001_initial.cpython-314.pyc b/Plataform_Web/temporadas/migrations/__pycache__/0001_initial.cpython-314.pyc new file mode 100644 index 0000000..44398ca Binary files /dev/null and b/Plataform_Web/temporadas/migrations/__pycache__/0001_initial.cpython-314.pyc differ diff --git a/Plataform_Web/temporadas/migrations/__pycache__/__init__.cpython-314.pyc b/Plataform_Web/temporadas/migrations/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..8b932b4 Binary files /dev/null and b/Plataform_Web/temporadas/migrations/__pycache__/__init__.cpython-314.pyc differ diff --git a/Plataform_Web/temporadas/models.py b/Plataform_Web/temporadas/models.py new file mode 100644 index 0000000..918a948 --- /dev/null +++ b/Plataform_Web/temporadas/models.py @@ -0,0 +1,30 @@ +from django.db import models + +class Temporada(models.Model): + # El nombre será algo como "Gades 2024-25" + nombre = models.CharField(max_length=50, unique=True) + + fecha_inicio = models.DateField() + fecha_fin = models.DateField() + + # Presupuesto total para ese año + presupuesto = models.DecimalField(max_digits=10, decimal_places=2, default=0.00) + + # Checkbox para marcar cuál es la temporada que estamos viviendo ahora + actual = models.BooleanField(default=False, verbose_name="¿Es la temporada actual?") + + 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 + + def __str__(self): + return self.nombre + + def save(self, *args, **kwargs): + # TRUCO PRO: Si marco esta temporada como "actual", desmarco todas las demás + # Así evitamos que haya dos temporadas activas a la vez. + if self.actual: + Temporada.objects.filter(actual=True).exclude(pk=self.pk).update(actual=False) + super().save(*args, **kwargs) \ No newline at end of file diff --git a/Plataform_Web/temporadas/tests.py b/Plataform_Web/temporadas/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/Plataform_Web/temporadas/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Plataform_Web/temporadas/views.py b/Plataform_Web/temporadas/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/Plataform_Web/temporadas/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/Plataform_Web/users/__init__.py b/Plataform_Web/users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Plataform_Web/users/__pycache__/__init__.cpython-314.pyc b/Plataform_Web/users/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..7f6fa4d Binary files /dev/null and b/Plataform_Web/users/__pycache__/__init__.cpython-314.pyc differ diff --git a/Plataform_Web/users/__pycache__/admin.cpython-314.pyc b/Plataform_Web/users/__pycache__/admin.cpython-314.pyc new file mode 100644 index 0000000..de243a5 Binary files /dev/null and b/Plataform_Web/users/__pycache__/admin.cpython-314.pyc differ diff --git a/Plataform_Web/users/__pycache__/apps.cpython-314.pyc b/Plataform_Web/users/__pycache__/apps.cpython-314.pyc new file mode 100644 index 0000000..4ee0e1e Binary files /dev/null and b/Plataform_Web/users/__pycache__/apps.cpython-314.pyc differ diff --git a/Plataform_Web/users/__pycache__/models.cpython-314.pyc b/Plataform_Web/users/__pycache__/models.cpython-314.pyc new file mode 100644 index 0000000..1835ea8 Binary files /dev/null and b/Plataform_Web/users/__pycache__/models.cpython-314.pyc differ diff --git a/Plataform_Web/users/admin.py b/Plataform_Web/users/admin.py new file mode 100644 index 0000000..b797f2c --- /dev/null +++ b/Plataform_Web/users/admin.py @@ -0,0 +1,18 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from .models import CustomUser + +class CustomUserAdmin(UserAdmin): + model = CustomUser + + # Esto añade una sección nueva al editar un usuario existente + fieldsets = UserAdmin.fieldsets + ( + ('Información del Equipo (TFG)', {'fields': ('rol', 'especialidad')}), + ) + + # Esto permite añadir estos campos cuando creas un usuario nuevo desde el admin + add_fieldsets = UserAdmin.add_fieldsets + ( + ('Información del Equipo (TFG)', {'fields': ('rol', 'especialidad')}), + ) + +admin.site.register(CustomUser, CustomUserAdmin) \ No newline at end of file diff --git a/Plataform_Web/users/apps.py b/Plataform_Web/users/apps.py new file mode 100644 index 0000000..4ce1fab --- /dev/null +++ b/Plataform_Web/users/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + name = 'users' diff --git a/Plataform_Web/users/migrations/0001_initial.py b/Plataform_Web/users/migrations/0001_initial.py new file mode 100644 index 0000000..fedfc57 --- /dev/null +++ b/Plataform_Web/users/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# Generated by Django 6.0.2 on 2026-02-17 10:36 + +import django.contrib.auth.models +import django.contrib.auth.validators +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='CustomUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('rol', models.CharField(choices=[('directiva', 'Directiva'), ('jefe_area', 'Jefe de Área'), ('empleado', 'Empleado')], default='empleado', max_length=20)), + ('especialidad', models.CharField(blank=True, choices=[('aerodinamica', 'Aerodinámica'), ('chasis', 'Chasis'), ('business', 'Business & Operations'), ('epowertrain', 'E-Powertrain'), ('electronica', 'Electrónica'), ('sdf', 'SDF'), ('motor_transmision', 'Motor & Transmisión'), ('software', 'Software')], max_length=30, null=True)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/Plataform_Web/users/migrations/0002_alter_customuser_especialidad_alter_customuser_rol.py b/Plataform_Web/users/migrations/0002_alter_customuser_especialidad_alter_customuser_rol.py new file mode 100644 index 0000000..9c991e1 --- /dev/null +++ b/Plataform_Web/users/migrations/0002_alter_customuser_especialidad_alter_customuser_rol.py @@ -0,0 +1,23 @@ +# Generated by Django 6.0.2 on 2026-02-17 11:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='especialidad', + field=models.CharField(blank=True, choices=[('aerodinamica', 'Aerodinámica'), ('chasis', 'Chasis'), ('business', 'Business & Operations'), ('epowertrain', 'E-Powertrain'), ('electronica', 'Electrónica'), ('sdf', 'SDF'), ('motor_transmision', 'Motor & Transmisión'), ('software', 'Software')], max_length=30, null=True, verbose_name='Área Técnica'), + ), + migrations.AlterField( + model_name='customuser', + name='rol', + field=models.CharField(choices=[('directiva', 'Directiva'), ('jefe_area', 'Jefe de Área'), ('empleado', 'Empleado')], default='empleado', max_length=20, verbose_name='Rol en el equipo'), + ), + ] diff --git a/Plataform_Web/users/migrations/__init__.py b/Plataform_Web/users/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Plataform_Web/users/migrations/__pycache__/0001_initial.cpython-314.pyc b/Plataform_Web/users/migrations/__pycache__/0001_initial.cpython-314.pyc new file mode 100644 index 0000000..725f115 Binary files /dev/null and b/Plataform_Web/users/migrations/__pycache__/0001_initial.cpython-314.pyc differ diff --git a/Plataform_Web/users/migrations/__pycache__/0002_alter_customuser_especialidad_alter_customuser_rol.cpython-314.pyc b/Plataform_Web/users/migrations/__pycache__/0002_alter_customuser_especialidad_alter_customuser_rol.cpython-314.pyc new file mode 100644 index 0000000..8d347ce Binary files /dev/null and b/Plataform_Web/users/migrations/__pycache__/0002_alter_customuser_especialidad_alter_customuser_rol.cpython-314.pyc differ diff --git a/Plataform_Web/users/migrations/__pycache__/__init__.cpython-314.pyc b/Plataform_Web/users/migrations/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..be754e9 Binary files /dev/null and b/Plataform_Web/users/migrations/__pycache__/__init__.cpython-314.pyc differ diff --git a/Plataform_Web/users/models.py b/Plataform_Web/users/models.py new file mode 100644 index 0000000..af3c0f0 --- /dev/null +++ b/Plataform_Web/users/models.py @@ -0,0 +1,43 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models + +class CustomUser(AbstractUser): + # --- 1. OPCIONES (El Menú) --- + ROL_CHOICES = ( + ('directiva', 'Directiva'), + ('jefe_area', 'Jefe de Área'), + ('empleado', 'Empleado'), + ) + + ESPECIALIDAD_CHOICES = ( + ('aerodinamica', 'Aerodinámica'), + ('chasis', 'Chasis'), + ('business', 'Business & Operations'), + ('epowertrain', 'E-Powertrain'), + ('electronica', 'Electrónica'), + ('sdf', 'SDF'), + ('motor_transmision', 'Motor & Transmisión'), + ('software', 'Software'), + ) + + # --- 2. CAMPOS (Las Columnas en la BD) --- + # Usamos la versión con 'verbose_name' porque queda mejor en la web + rol = models.CharField( + max_length=20, + choices=ROL_CHOICES, + default='empleado', + verbose_name="Rol en el equipo" + ) + + especialidad = models.CharField( + max_length=30, + choices=ESPECIALIDAD_CHOICES, + null=True, + blank=True, + verbose_name="Área Técnica" + ) + + # --- 3. MÉTODOS --- + def __str__(self): + # Muestra: "Nombre Apellido (usuario) - Rol" + return f"{self.first_name} {self.last_name} ({self.username}) - {self.get_rol_display()}" \ No newline at end of file diff --git a/Plataform_Web/users/tests.py b/Plataform_Web/users/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/Plataform_Web/users/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Plataform_Web/users/views.py b/Plataform_Web/users/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/Plataform_Web/users/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.