commit 1e78ad3a40e38e8edd5704ad73e9fbd61fa55283 Author: minguezsanzjuanjose Date: Sat Apr 11 03:31:05 2026 +0200 Initial commit diff --git a/.env b/.env new file mode 100644 index 0000000..7c504b4 --- /dev/null +++ b/.env @@ -0,0 +1,10 @@ +# Seguridad +DEBUG=True +SECRET_KEY=una-clave-muy-secreta-y-larga-123456 + +# Base de Datos (Conectando al PostgreSQL que instalamos) +DB_NAME=gitea +DB_USER=gitea +DB_PASSWORD=gitea_password +DB_HOST=db +DB_PORT=5432 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3dcc296 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# Usamos una imagen ligera de Python +FROM python:3.12-slim + +# Evitar que Python genere archivos .pyc y que el buffer se sature +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +# Directorio de trabajo +WORKDIR /app + +# Instalar dependencias del sistema necesarias +RUN apt-get update && apt-get install -y \ + libpq-dev \ + gcc \ + gettext \ + && rm -rf /var/lib/apt/lists/* + +# Instalar dependencias de Python +COPY requirements.txt /app/ +RUN pip install --no-cache-dir -r requirements.txt + +# Copiar el resto del código +COPY . /app/ + +# Exponer el puerto de Django +EXPOSE 8000 + +# Comando por defecto para arrancar (usaremos manage.py en dev y gunicorn en prod) +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..10becf4 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# django-core-base + diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/common/utils.py b/apps/common/utils.py new file mode 100644 index 0000000..42be29d --- /dev/null +++ b/apps/common/utils.py @@ -0,0 +1,11 @@ +# Ejemplo de limpieza de tipos para SQL +def clean_sql_string(value): + if value is None: + return "" + return str(value).strip().replace("'", "''") + +def clean_sql_int(value): + try: + return int(value) + except (ValueError, TypeError): + return 0 \ No newline at end of file diff --git a/apps/promociones/actions.py b/apps/promociones/actions.py new file mode 100644 index 0000000..8f8d48e --- /dev/null +++ b/apps/promociones/actions.py @@ -0,0 +1,56 @@ +from django.db import connection +from common.utils import clean_sql_string, clean_sql_int + +def getData(params): + """ + Función estándar para obtener datos de promociones. + """ + # 1. Definimos la query con placeholders (%s) + query = """ + SELECT id, nombre, fecha_inicio, descripcion + FROM promociones + WHERE id = %s AND activo = %s + """ + + # 2. Preparamos el diccionario de parámetros (tu estándar get_parameterized) + # Limpiamos los datos antes de enviarlos a la base de datos + id_promocion = clean_sql_int(params.get('id')) + is_active = 1 if params.get('activo') else 0 + + parameter_dict = [id_promocion, is_active] + + # 3. Ejecución parametrizada + with connection.cursor() as cursor: + cursor.execute(query, parameter_dict) + columns = [col[0] for col in cursor.description] + result = [dict(zip(columns, row)) for row in cursor.fetchall()] + + return result + +def setData(params): + """ + Función estándar para insertar o actualizar (set_parameterized). + """ + # Ejemplo de UPDATE con JOIN (como solicitaste en tu estándar) + query = """ + UPDATE promociones p + SET p.nombre = %s, p.fecha_modificacion = %s + FROM categorias c + WHERE p.categoria_id = c.id AND p.id = %s + """ + + # Fecha en formato Año-Día-Mes (Y-d-m) + import datetime + fecha_hoy = datetime.datetime.now().strftime('%Y-%d-%m') + + parameter_dict = [ + clean_sql_string(params.get('nombre')), + fecha_hoy, + clean_sql_int(params.get('id')) + ] + + with connection.cursor() as cursor: + cursor.execute(query, parameter_dict) + affected_rows = cursor.rowcount + + return affected_rows \ No newline at end of file diff --git a/apps/promociones/urls.py b/apps/promociones/urls.py new file mode 100644 index 0000000..cb85abd --- /dev/null +++ b/apps/promociones/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from .views import get_promocion_view + +urlpatterns = [ + # Capa 1: Definición del endpoint + path('obtener/', get_promocion_view, name='get_promocion'), +] \ No newline at end of file diff --git a/apps/promociones/views.py b/apps/promociones/views.py new file mode 100644 index 0000000..5624e22 --- /dev/null +++ b/apps/promociones/views.py @@ -0,0 +1,54 @@ +import logging +from django.http import JsonResponse +from .actions import getData + +# Configuración del logger para rastrear la ejecución +logger = logging.getLogger(__name__) + +def get_promocion_view(request): + """ + Vista estandarizada para la obtención de una promoción. + """ + + # --------------------------------------------------------- + # BLOQUE 1: Log de iniciación + # --------------------------------------------------------- + logger.info("[START] Iniciando ejecución de get_promocion_view") + + try: + # --------------------------------------------------------- + # BLOQUE 2: Limpieza de datos (Data Cleaning) + # --------------------------------------------------------- + # Extraemos los parámetros del request y preparamos el diccionario + raw_data = request.GET.dict() + + # Aquí es donde ella aplicaría validaciones adicionales si fuera necesario + clean_params = { + 'id': raw_data.get('id'), + 'activo': raw_data.get('activo', True) # Valor por defecto + } + + # --------------------------------------------------------- + # BLOQUE 3: Llamada a la Action (Execution) + # --------------------------------------------------------- + # La lógica de SQL y parametrización vive dentro de esta llamada + resultado_db = getData(clean_params) + + # --------------------------------------------------------- + # BLOQUE 4: Log de cierre y respuesta (Closure) + # --------------------------------------------------------- + logger.info(f"[SUCCESS] get_promocion_view finalizada. Registros encontrados: {len(resultado_db)}") + + return JsonResponse({ + 'status': 'success', + 'data': resultado_db + }, status=200) + + except Exception as e: + # Log de error detallado en caso de fallo + logger.error(f"[ERROR] Fallo crítico en get_promocion_view: {str(e)}") + + return JsonResponse({ + 'status': 'error', + 'message': 'Error interno del servidor' + }, status=500) \ No newline at end of file diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/settings.py b/core/settings.py new file mode 100644 index 0000000..36c1ad3 --- /dev/null +++ b/core/settings.py @@ -0,0 +1,20 @@ +from dotenv import load_dotenv +import os + +# Cargar variables desde el archivo .env +load_dotenv() + +SECRET_KEY = os.getenv('SECRET_KEY') +DEBUG = os.getenv('DEBUG') == 'True' + +# Configuración de base de datos usando las variables +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.getenv('DB_NAME'), + 'USER': os.getenv('DB_USER'), + 'PASSWORD': os.getenv('DB_PASSWORD'), + 'HOST': os.getenv('DB_HOST'), + 'PORT': os.getenv('DB_PORT'), + } +} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py new file mode 100644 index 0000000..ddb5a6d --- /dev/null +++ b/core/urls.py @@ -0,0 +1,6 @@ +from django.urls import path, include + +urlpatterns = [ + # Redirigimos todas las peticiones de /api/promociones/ a nuestra app + path('api/promociones/', include('promociones.urls')), +] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..867c6af --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +networks: + # Usamos la red que creamos al principio para que el Proxy lo vea + frontend: + external: true + +services: + web: + build: . + container_name: django_app_dev + restart: always + environment: + - DEBUG=1 + - DATABASE_URL=postgres://gitea:gitea_password@db:5432/gitea + # Usamos la base de datos de Gitea para empezar, o luego creamos una aparte + volumes: + - .:/app + networks: + - frontend + ports: + - "8000:8000" \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..8236117 --- /dev/null +++ b/manage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + +def main(): + """Run administrative tasks.""" + # Apuntamos a la configuración dentro de la carpeta 'core' + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.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() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..99cc37b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +Django==5.0.3 +psycopg2-binary==2.9.9 +gunicorn==21.2.0 +python-dotenv==1.0.1 \ No newline at end of file