# Convenciones del Ecosistema V-Encore Lab ## Ecosistema de microservicios | Repo | Rol | Puerto | |------|-----|--------| | `django-core-base` | Hub orquestador principal | 8000 | | `api_backoffice` | Consulta y gestión de BD | 8001 | | `api_comunicaciones` | Emails, notificaciones, webhooks | 8002 | | `api_documentacion` | Generación y gestión de documentos | 8003 | | `web_interno` | Panel de gestión (React + Ant Design) | 3000 | ## Stack Django - Django 5.0 + DRF + SimpleJWT - Apps bajo `app/`. Prefijo `/api/` en todas las URLs salvo `admin/`. - Patrón 3 capas: **URL → View → Action** - SQL con `connection.cursor()` y placeholders (`%s`). Nunca concatenar strings. ## Patrón de vistas — 4 bloques obligatorios ```python from general.utilidades.acciones import LogService class MiVista(APIView): authentication_classes = [JWTAuthentication] permission_classes = [IsAuthenticated] def post(self, request): path = '/mi-app/mi-endpoint/' # Bloque 1 — Inicio Log log_id = LogService.gestionar_log(self, request, path=path) try: # Bloque 2 — Data Cleaning data = request.data LogService.gestionar_log(self, request, log_id=log_id, path=path, body_request=data, status_code=100) params = {'campo': data.get('campo')} except Exception as error: response = {'error': str(error)} LogService.gestionar_log(self, request, log_id=log_id, path=path, body_response=response, status_code=400) return JsonResponse(response, status=400) try: # Bloque 3 — Action Call resultado = mi_accion(params) # Bloque 4 — Cierre Log LogService.gestionar_log(self, request, log_id=log_id, path=path, body_response=resultado, status_code=200) return JsonResponse(resultado, status=200) except Exception as error: response = {'error': str(error)} LogService.gestionar_log(self, request, log_id=log_id, path=path, body_response=response, status_code=500) return JsonResponse(response, status=500) ``` ## LogService — reglas - Siempre llamar como `LogService.gestionar_log(self, request, ...)` — pasar `self` de la vista. - Primera llamada (sin `log_id`): crea el registro, devuelve el `log_id`. - Llamadas siguientes: actualizar con `log_id=log_id`. - `body_request` y `body_response` se serializan con `DjangoJSONEncoder` — soporta `datetime.date`, `Decimal`, etc. - `status_code` como entero. Usar `is not None` para comprobar (0 es válido). ## Base de datos - `DB_HOST` definido → PostgreSQL - `DB_HOST` no definido → SQLite en `app/data/db.sqlite3` - Migraciones siempre desde `app/`: `cd app && python manage.py migrate` - SQL INSERT con psycopg2: usar `INSERT ... RETURNING id` + `cursor.fetchone()`. **Nunca `cursor.lastrowid`**. - Booleans en psycopg2: pasar `True`/`False`, no `1`/`0`. ## Flujo de ramas ``` pre-dev → dev → master ``` - `pre-dev`: desarrollo activo — aquí entran todos los cambios - `dev`: validación previa a producción - `master`: producción estable - Merges siempre con `--no-ff` - **NUNCA** propagar en sentido inverso (master → dev o dev → pre-dev) - Secuencia correcta: ```bash git checkout pre-dev && git merge --no-ff git checkout dev && git merge pre-dev --no-ff && git push origin dev git checkout master && git merge dev --no-ff && git push origin master ``` ## Estructura de app Django ``` app/ ├── api_config/ # settings.py, urls.py, wsgi.py ├── general/ # LogService, utils — NO tiene modelos propios ├── backend_admin/ # Modelo Log (audit_logs), admin panel ├── common/ # Modelos y utilidades compartidas entre apps ├── / # Una app por dominio de negocio ├── data/ # SQLite (gitignored, solo .gitkeep versionado) └── manage.py ``` ## Fixtures - `loaddata` ignora `auto_now_add=True` — incluir fechas explícitas en el JSON o falla con NOT NULL en PostgreSQL.