refactor: reorganizar estructura del proyecto al estándar app/api_config
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good

- core/ → app/api_config/
- apps/backend_admin/ → app/backend_admin/
- apps/common/ → app/common/
- apps/promociones/ → app/promociones/
- manage.py → app/manage.py
- Añadir app/requirements.txt
- Actualizar todos los imports y referencias (DJANGO_SETTINGS_MODULE, ROOT_URLCONF, WSGI_APPLICATION, INSTALLED_APPS)
- Actualizar Dockerfile con nuevo WORKDIR

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
juanjo
2026-04-16 16:01:17 +02:00
parent 27ccce862d
commit 299428741b
28 changed files with 51 additions and 24 deletions

View File

@@ -0,0 +1,64 @@
from django.db import connection
from common.utils import clean_sql_string, clean_sql_int
def get_status_action():
"""
Acción simple para comprobar que la lógica de la API responde.
"""
return {"status": "ok", "message": "API is running"}
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 = True if params.get('activo') else False
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 YYYY-MM-DD para SQL
import datetime
fecha_hoy = datetime.datetime.now().strftime('%Y-%m-%d')
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

5
app/promociones/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class PromocionesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'promociones'

View File

@@ -0,0 +1,35 @@
[
{
"model": "promociones.promocion",
"pk": 1,
"fields": {
"nombre": "Oferta de Bienvenida",
"fecha_inicio": "2026-04-01",
"descripcion": "Descuento para nuevos usuarios",
"activo": true,
"categoria_id": 1
}
},
{
"model": "promociones.promocion",
"pk": 2,
"fields": {
"nombre": "Promo Primavera",
"fecha_inicio": "2026-05-01",
"descripcion": "Todo al 20% de descuento",
"activo": true,
"categoria_id": 2
}
},
{
"model": "promociones.promocion",
"pk": 3,
"fields": {
"nombre": "Liquidación Stock",
"fecha_inicio": "2026-04-14",
"descripcion": "Últimas unidades",
"activo": false,
"categoria_id": 1
}
}
]

View File

@@ -0,0 +1 @@
# Archivo para marcar esta carpeta como paquete de migraciones

20
app/promociones/models.py Normal file
View File

@@ -0,0 +1,20 @@
from django.db import models
class Promocion(models.Model):
# Campos detectados en tu getData y setData
nombre = models.CharField(max_length=255)
fecha_inicio = models.DateField(null=True, blank=True)
fecha_modificacion = models.DateField(null=True, blank=True)
descripcion = models.TextField(null=True, blank=True)
activo = models.BooleanField(default=True)
# Campo usado en tu JOIN de setData
categoria_id = models.IntegerField(null=True, blank=True)
class Meta:
# ¡CRÍTICO! Esto le dice a Django que la tabla se llame exactamente
# como la has escrito en tu SQL manual
db_table = 'promociones'
def __str__(self):
return self.nombre

6
app/promociones/urls.py Normal file
View File

@@ -0,0 +1,6 @@
from django.urls import path
from .views import PromocionObtener
urlpatterns = [
path('obtener/', PromocionObtener.as_view(), name='obtener_promocion'),
]

46
app/promociones/views.py Normal file
View File

@@ -0,0 +1,46 @@
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
from django.http import JsonResponse
from backend_admin.models import Log
from .acciones import getData
from django.utils import timezone # Esta es la forma correcta
class PromocionObtener(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
# --- BLOQUE 1: LOG INITIATION ---
log_entry = Log.objects.create(
user=request.user.username,
path='promociones/obtener/',
method='POST',
status_code='0'
)
print('llega a despues de log entry')
try:
# --- BLOQUE 2: DATA CLEANING ---
data = request.data
log_entry.request = data
log_entry.save()
params = {'id': data.get('id'), 'activo': data.get('activo')}
# --- BLOQUE 3: ACTION CALL ---
# Aquí ya recibimos las fechas como strings gracias al paso 1
resultado = getData(params)
# --- BLOQUE 4: LOG CLOSURE & RESPONSE ---
log_entry.response = {"count": len(resultado)} # No guardes todo el JSON si es muy grande
log_entry.status_code = '200'
log_entry.save()
return JsonResponse(resultado, safe=False, status=200)
except Exception as e:
log_entry.status_code = '500'
log_entry.response = {'error': str(e)}
log_entry.save()
return JsonResponse({'error': str(e)}, status=500)