Compare commits

21 Commits

Author SHA1 Message Date
juanjo
c4db3639c3 Merge pre-dev: docs flujo ramas
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good
2026-04-17 01:15:19 +02:00
juanjo
c692ce2a61 sync
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good
2026-04-17 01:15:05 +02:00
juanjo
152e9c14ef Merge: docs flujo ramas 2026-04-17 01:15:05 +02:00
juanjo
99de5f06b5 docs: corregir flujo de ramas en CLAUDE.md (pre-dev → dev → master)
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 01:15:04 +02:00
juanjo
379ce825f6 Merge pre-dev: fix api_hub_dispatcher → api_config
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good
2026-04-17 01:13:56 +02:00
juanjo
d7a84a4dfa Merge master: fix api_hub_dispatcher → api_config
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good
2026-04-17 01:13:54 +02:00
juanjo
fbc5f0f6c4 fix: reemplazar referencias a api_hub_dispatcher por api_config
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good
ROOT_URLCONF, WSGI_APPLICATION, DJANGO_SETTINGS_MODULE apuntaban al
módulo renombrado — causaba ModuleNotFoundError al arrancar en Docker.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 01:13:52 +02:00
juanjo
abe67a7f20 Merge pre-dev: fix env_file docker-compose
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good
2026-04-17 01:06:34 +02:00
juanjo
cac00a4f8c Merge master: fix env_file docker-compose
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good
2026-04-17 01:06:31 +02:00
juanjo
a17f00bad2 fix: env_file .env (relativo a deployments/) en docker-compose
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good
Jenkins copia el secret a deployments/.env — el path debe ser .env
relativo al docker-compose.yml, no ../.env a la raíz del workspace.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 01:06:28 +02:00
juanjo
5367ba7313 Merge pre-dev: fix env_file path docker-compose
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good
2026-04-17 01:05:40 +02:00
juanjo
b8c3e03348 fix: corregir env_file path en docker-compose (../.env → .env)
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good
Jenkins copia el secret a deployments/.env — el path debe ser relativo
al docker-compose.yml, no a la raíz del workspace.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 01:05:37 +02:00
juanjo
23cd4bf8b4 Merge pre-dev: gunicorn + wait-for-db + healthcheck
Some checks failed
DEPLOY_MULTI_BRACH/pipeline/head There was a failure building this commit
2026-04-17 00:39:30 +02:00
juanjo
b43ca9d919 Merge branch 'dev' of https://git.v-encore-lab.com/Proyecto-SaaS/django-core-base into dev 2026-04-17 00:39:30 +02:00
juanjo
252e176e9d Merge master: gunicorn + wait-for-db + healthcheck
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good
2026-04-17 00:39:27 +02:00
juanjo
41fc2a0aa0 fix: gunicorn + wait-for-db + healthcheck en docker-compose
Some checks failed
DEPLOY_MULTI_BRACH/pipeline/head There was a failure building this commit
- entrypoint.sh: sustituye runserver por gunicorn (workers=2, timeout=120)
- entrypoint.sh: espera a PostgreSQL antes de migrar cuando DB_HOST está definido
- docker-compose.yml: unifica nombre de servicio db, añade healthcheck robusto,
  corrige env_file path a ../.env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 00:39:21 +02:00
juanjo
001bf13d26 chore: añadir CLAUDE.md con convenciones del ecosistema
All checks were successful
DEPLOY_MULTI_BRACH/pipeline/head This commit looks good
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 00:12:18 +02:00
juanjo
adaf20bd8e Merge pre-dev: revert api_config 2026-04-16 23:31:29 +02:00
juanjo
30c540b6f9 revert: restaurar nombre api_config (revertir renombrado erróneo)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 23:31:08 +02:00
juanjo
95e036108d Merge pre-dev: renombrar api_config → api_hub_dispatcher 2026-04-16 23:28:44 +02:00
juanjo
778753afd6 refactor: renombrar proyecto principal api_config → api_hub_dispatcher
- Renombrar carpeta app/api_config/ → app/api_hub_dispatcher/
- Actualizar DJANGO_SETTINGS_MODULE en asgi.py, wsgi.py, manage.py
- Actualizar ROOT_URLCONF y WSGI_APPLICATION en settings.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 23:28:43 +02:00
7 changed files with 162 additions and 33 deletions

110
CLAUDE.md Normal file
View File

@@ -0,0 +1,110 @@
# 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 <mi-cambio> --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
├── <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.

View File

@@ -1,4 +1,4 @@
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api_hub_dispatcher.settings')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api_config.settings')
application = get_asgi_application()

View File

@@ -74,7 +74,7 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'api_hub_dispatcher.urls'
ROOT_URLCONF = 'api_config.urls'
TEMPLATES = [
{
@@ -92,7 +92,7 @@ TEMPLATES = [
},
]
WSGI_APPLICATION = 'api_hub_dispatcher.wsgi.application'
WSGI_APPLICATION = 'api_config.wsgi.application'
# 3. DATABASE
# En producción (cuando DB_HOST está definido) usa PostgreSQL.

View File

@@ -2,6 +2,6 @@ import os
from django.core.wsgi import get_wsgi_application
# Este es el enlace con tus configuraciones
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api_hub_dispatcher.settings')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api_config.settings')
application = get_wsgi_application()

View File

@@ -1,44 +1,40 @@
version: '3.8'
services:
gitea-db:
image: postgres:15
# Usará el nombre de tu .env (django_db_local)
container_name: ${DB_CONTAINER_NAME:-django_db_dev}
restart: always
db:
image: postgres:15-alpine
container_name: ${DB_CONTAINER_NAME:-django_core_db}
restart: unless-stopped
environment:
POSTGRES_DB: ${DB_NAME:-gitea}
POSTGRES_USER: ${DB_USER:-gitea}
POSTGRES_PASSWORD: ${DB_PASSWORD:-gitea}
POSTGRES_DB: ${DB_NAME:-django_core_db}
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
volumes:
- postgres_data:/var/lib/postgresql/data
# --- ESTO ES LO QUE FALTA ---
ports:
- "${DATABASE_EXPOSE_PORT:-5432}:5432"
# ----------------------------
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-gitea} -d ${DB_NAME:-gitea}"]
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-django_core_db}"]
interval: 5s
timeout: 5s
retries: 5
retries: 10
start_period: 10s
web:
build:
context: ..
dockerfile: deployments/Dockerfile
container_name: ${APP_CONTAINER_NAME:-django_app_dev}
restart: always
container_name: ${APP_CONTAINER_NAME:-django_core_app}
restart: unless-stopped
env_file:
- .env
environment:
- DEBUG=${DEBUG_MODE:-1}
# IMPORTANTE: Este nombre debe coincidir con el nombre del servicio arriba (gitea-db)
- DB_HOST=gitea-db
- DB_HOST=db
- DB_PORT=5432
ports:
- "${PORT:-8000}:8000"
depends_on:
gitea-db:
db:
condition: service_healthy
volumes:

View File

@@ -3,16 +3,39 @@
# Salir inmediatamente si un comando falla
set -e
# --- Esperar a PostgreSQL si estamos en modo BD remota ---
if [ -n "$DB_HOST" ]; then
echo "--> Esperando a PostgreSQL en $DB_HOST:${DB_PORT:-5432}..."
until python -c "
import sys, psycopg2, os
try:
psycopg2.connect(
host=os.environ['DB_HOST'],
port=os.environ.get('DB_PORT', 5432),
user=os.environ['DB_USER'],
password=os.environ['DB_PASSWORD'],
dbname=os.environ['DB_NAME']
)
sys.exit(0)
except Exception:
sys.exit(1)
" 2>/dev/null; do
echo " PostgreSQL no disponible, reintentando en 2s..."
sleep 2
done
echo "--> PostgreSQL listo."
fi
echo "--> Ejecutando migraciones..."
# Esto asegura que si hay cambios en models.py, se generen y apliquen las tablas
python manage.py makemigrations --noinput
python manage.py migrate --noinput
echo "--> Cargando datos de prueba..."
# Este comando busca archivos JSON en las carpetas 'fixtures' de tus apps
# Usamos || true para que si el archivo no existe o ya están cargados, el contenedor no se detenga
python manage.py loaddata semillas || echo "Aviso: No se pudieron cargar las semillas (fichero no encontrado o error de formato)."
echo "--> Cargando semillas (si existen)..."
python manage.py loaddata semillas 2>/dev/null || echo " Sin semillas, continuando."
echo "--> Arrancando el servidor Django..."
# Usamos exec para que Django sea el proceso principal (PID 1) y reciba señales de Docker
exec python manage.py runserver 0.0.0.0:8000
echo "--> Arrancando servidor con Gunicorn..."
exec gunicorn api_config.wsgi:application \
--bind 0.0.0.0:8000 \
--workers 2 \
--timeout 120 \
--access-logfile - \
--error-logfile -