# 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
- `dev`: validación previa a producción
- `master`: producción estable
- Merges siempre con `--no-ff`

## 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
├── <feature_app>/    # 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.
