diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a57cd00 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,103 @@ +# 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 +├── / # 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.