Files
CRM_AGENTES_CARGA/docker-compose.prod.yml
marcos 61af5c80fe chore(deploy): las ocho variables del carril hacia EFC en produccion
El compose de produccion no declaraba ninguna EFC_*, asi que el carril nacia muerto
alla: `EfcClient.is_configured` era False y todo el enganche hacia no-op en silencio.

Van en los TRES servicios y no solo en el backend, porque cada uno hace una parte:

  - backend       encola al crear el expediente y despacha la entrega inmediata;
  - celery_worker EJECUTA la entrega (`deliver_outbox_row` / `deliver_file_outbox_row`).
                  Si solo el backend las tuviera, el encolado se veria perfecto y nada
                  se entregaria nunca -- es exactamente el modo de fallo que ya nos
                  costo un diagnostico hoy, cuando el worker no tenia registradas las
                  tareas del carril;
  - celery_beat   dispara los tres barridos, que son la red que atrapa lo que el
                  despacho inmediato no alcanzo. Sin el, una caida de EFC deja la cola
                  detenida para siempre.

Se declaran las ocho aunque seis queden vacias por default. No es simetria: una
variable no declarada en el compose NO llega al contenedor, asi que editarla en el .env
no surte efecto y el sintoma parece un problema de red. Ya paso con EFC_API_VERIFY_SSL
en el compose de desarrollo, que solo pasa dos de las ocho.

EFC_API_URL vacia = carril apagado, y es el default a proposito: desplegar este cambio
no enciende nada. Encenderlo exige EFC_API_URL y una EFC_API_KEY identica a la
CRM_INTEGRATION_API_KEY del lado de EFC, cuyo permiso es fail-closed.

Ref: T2026-08-046

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 16:23:38 -06:00

366 lines
14 KiB
YAML

services:
# PostgreSQL - Base de datos core (app)
postgres-app:
image: postgres:18-alpine
container_name: app-postgres-app
environment:
POSTGRES_DB: app_core
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_APP_PASSWORD:-postgres}
POSTGRES_INITDB_ARGS: "--encoding=UTF8"
ports:
- "5939:5432"
volumes:
- postgres_app_data:/var/lib/postgresql/data
networks:
- backend-net
restart: unless-stopped
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U postgres -d app_core || exit 1" ]
interval: 5s
timeout: 3s
retries: 10
start_period: 20s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
shm_size: 128mb
# Backend - FastAPI
backend:
image: dev.aduanasoft.com/anexo76/backend:latest
container_name: app-backend
environment:
- DEBUG=${DEBUG:-True}
- ENVIRONMENT=${ENVIRONMENT:-production}
- PYTHONUNBUFFERED=1
- PYTHONDONTWRITEBYTECODE=1
- CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76}
- CORE_DB_PORT=${CORE_DB_PORT:-5432}
- CORE_DB_NAME=${CORE_DB_NAME:-app_core}
- CORE_DB_USER=${CORE_DB_USER:-postgres}
- CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres}
# Keycloak — fuente única de autenticación: Workspace (workspace.aduanasoft.com)
- KEYCLOAK_SERVER_URL=${KEYCLOAK_SERVER_URL:-https://workspace.aduanasoft.com/kcauth}
- KEYCLOAK_REALM=${KEYCLOAK_REALM:-master}
- KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-app-frontend}
- KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-}
- CORS_ORIGINS=${CORS_ORIGINS:-https://anexo76-dev.aduanasoft.com,http://localhost:3000}
- SITAR_API_URL=${SITAR_API_URL}
- SITAR_API_USER=${SITAR_API_USER}
- SITAR_API_PASSWORD=${SITAR_API_PASSWORD}
- COVE_FIEL_HASH_KEY=${COVE_FIEL_HASH_KEY}
- COVE_FIEL_HASH_IV=${COVE_FIEL_HASH_IV}
- COVE_API_URL=${COVE_API_URL:-https://api.vu.aduanasoft.com}
- COVE_API_VERIFY_SSL=${COVE_API_VERIFY_SSL:-False}
- VALKEY_URL=${VALKEY_URL:-redis://valkey:6379/0}
- CENTRAL_SERVER_URL=${CENTRAL_SERVER_URL:-""}
- SYNC_SECRET_TOKEN=${SYNC_SECRET_TOKEN:-change-this-sync-token-in-production}
- SPOKE_URLS=${SPOKE_URLS:-""}
- HUB_URL=${HUB_URL:-https://workspace.aduanasoft.com}
- HUB_ADMIN_EMAIL=${HUB_ADMIN_EMAIL:-}
- HUB_ADMIN_PASSWORD=${HUB_ADMIN_PASSWORD:-}
- APP_PUBLIC_URL=${APP_PUBLIC_URL:-https://anexo76-dev.aduanasoft.com}
- SMTP_HOST=${SMTP_HOST:-smtp.gmail.com}
- SMTP_PORT=${SMTP_PORT:-587}
- SMTP_USER=${SMTP_USER:-}
- SMTP_PASSWORD=${SMTP_PASSWORD:-}
- SMTP_FROM_NAME=${SMTP_FROM_NAME:-Sistema Anexo76}
- SMTP_USE_TLS=${SMTP_USE_TLS:-true}
- CSV_IMPORT_STORAGE=${CSV_IMPORT_STORAGE:-minio}
- S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://minio:9000}
- S3_ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}}
- S3_SECRET_KEY=${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}}
- S3_BUCKET=${S3_BUCKET:-app}
- S3_REGION=${S3_REGION:-us-east-1}
- S3_USE_SSL=${S3_USE_SSL:-false}
- S3_FILE_STORAGE=${S3_FILE_STORAGE:-true}
- S3_PRESIGNED_EXPIRES_SECONDS=${S3_PRESIGNED_EXPIRES_SECONDS:-3600}
# ── Carril hacia EFC, el expediente electronico (T2026-08-046) ──────────────────────
# EFC_API_URL VACIA = CARRIL APAGADO. No es un error de configuracion: con la URL vacia
# `EfcClient.is_configured` es False, todo el enganche hace no-op y el CRM guarda sus
# archivos solo en su propio MinIO. Encenderlo exige las dos primeras.
#
# EFC_API_KEY debe ser IDENTICA a CRM_INTEGRATION_API_KEY del lado de EFC. Alla el permiso
# es fail-closed: si no coinciden, EFC responde 403 "API key invalida o no configurada" y
# esa respuesta NO distingue entre clave equivocada y clave ausente, a proposito.
#
# Las cuatro ultimas van declaradas aunque hoy queden vacias. La razon no es simetria: si
# una variable no se declara aqui, editarla en el .env NO surte efecto y el sintoma parece
# un problema de red. Ya paso con EFC_API_VERIFY_SSL en el compose de desarrollo.
- EFC_API_URL=${EFC_API_URL:-}
- EFC_API_KEY=${EFC_API_KEY:-}
- EFC_API_VERIFY_SSL=${EFC_API_VERIFY_SSL:-true}
- EFC_API_TIMEOUT_MS=${EFC_API_TIMEOUT_MS:-8000}
# Las subidas van aparte: 8 s no alcanzan para un archivo de 25 MB. Debe quedar POR DEBAJO
# del proxy_read_timeout del nginx de EFC.
- EFC_UPLOAD_TIMEOUT_MS=${EFC_UPLOAD_TIMEOUT_MS:-55000}
# Scaffolding de mTLS: activarlo es configuracion, no codigo. Es el endurecimiento que la
# deuda de la API key estatica pide antes de produccion real.
- EFC_MTLS_CA_PATH=${EFC_MTLS_CA_PATH:-}
- EFC_MTLS_CERT_PATH=${EFC_MTLS_CERT_PATH:-}
- EFC_MTLS_KEY_PATH=${EFC_MTLS_KEY_PATH:-}
ports:
- "3467:8000"
depends_on:
postgres-app:
condition: service_healthy
minio:
condition: service_healthy
volumes:
- backend_uploads:/app/uploads
- backend_layouts:/app/layouts
networks:
- backend-net
- frontend-net
restart: unless-stopped
command: [ "gunicorn", "main:app", "-k", "uvicorn.workers.UvicornWorker", "-w", "${WEB_CONCURRENCY:-1}", "-b", "0.0.0.0:8000", "--log-level", "info", "--forwarded-allow-ips", "*" ]
healthcheck:
test: [ "CMD-SHELL", "curl -f http://localhost:8000/api/health || exit 1" ]
interval: 15s
timeout: 5s
retries: 5
start_period: 60s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
# celery
celery_worker:
image: dev.aduanasoft.com/anexo76/backend:latest
container_name: worker
command: celery -A core.celery_app worker --loglevel=info
environment:
- VALKEY_URL=${VALKEY_URL:-redis://valkey:6379/0}
- CENTRAL_SERVER_URL=${CENTRAL_SERVER_URL:-""}
- SYNC_SECRET_TOKEN=${SYNC_SECRET_TOKEN:-change-this-sync-token-in-production}
- SPOKE_URLS=${SPOKE_URLS:-""}
- CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76}
- CORE_DB_PORT=${CORE_DB_PORT:-5432}
- CORE_DB_NAME=${CORE_DB_NAME:-app_core}
- CORE_DB_USER=${CORE_DB_USER:-postgres}
- CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres}
- SITAR_API_URL=${SITAR_API_URL}
- SITAR_API_USER=${SITAR_API_USER}
- SITAR_API_PASSWORD=${SITAR_API_PASSWORD}
- COVE_FIEL_HASH_KEY=${COVE_FIEL_HASH_KEY}
- COVE_FIEL_HASH_IV=${COVE_FIEL_HASH_IV}
- COVE_API_URL=${COVE_API_URL:-https://api.vu.aduanasoft.com}
- COVE_API_VERIFY_SSL=${COVE_API_VERIFY_SSL:-False}
- CSV_IMPORT_STORAGE=${CSV_IMPORT_STORAGE:-minio}
- S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://minio:9000}
- S3_ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}}
- S3_SECRET_KEY=${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}}
- S3_BUCKET=${S3_BUCKET:-app}
- S3_REGION=${S3_REGION:-us-east-1}
- S3_USE_SSL=${S3_USE_SSL:-false}
- S3_FILE_STORAGE=${S3_FILE_STORAGE:-true}
# Carril hacia EFC (T2026-08-046). EL WORKER LAS NECESITA IGUAL QUE EL BACKEND: la entrega
# de expedientes y archivos la hace `deliver_*_row` AQUI, no en el proceso web. Si solo el
# backend las tuviera, el encolado se veria perfecto y nada se entregaria nunca.
# Ver la nota completa en el servicio `backend`.
- EFC_API_URL=${EFC_API_URL:-}
- EFC_API_KEY=${EFC_API_KEY:-}
- EFC_API_VERIFY_SSL=${EFC_API_VERIFY_SSL:-true}
- EFC_API_TIMEOUT_MS=${EFC_API_TIMEOUT_MS:-8000}
- EFC_UPLOAD_TIMEOUT_MS=${EFC_UPLOAD_TIMEOUT_MS:-55000}
- EFC_MTLS_CA_PATH=${EFC_MTLS_CA_PATH:-}
- EFC_MTLS_CERT_PATH=${EFC_MTLS_CERT_PATH:-}
- EFC_MTLS_KEY_PATH=${EFC_MTLS_KEY_PATH:-}
depends_on:
- backend
- valkey
volumes:
- backend_layouts:/app/layouts
networks:
- backend-net
celery_beat:
image: dev.aduanasoft.com/anexo76/backend:latest
container_name: celery_beat
command: celery -A core.celery_app beat --loglevel=info
environment:
- VALKEY_URL=${VALKEY_URL:-redis://valkey:6379/0}
- CENTRAL_SERVER_URL=${CENTRAL_SERVER_URL:-""}
- SYNC_SECRET_TOKEN=${SYNC_SECRET_TOKEN:-change-this-sync-token-in-production}
- SPOKE_URLS=${SPOKE_URLS:-""}
- CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76}
- CORE_DB_PORT=${CORE_DB_PORT:-5432}
- CORE_DB_NAME=${CORE_DB_NAME:-app_core}
- CORE_DB_USER=${CORE_DB_USER:-postgres}
- CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres}
- SITAR_API_URL=${SITAR_API_URL}
- SITAR_API_USER=${SITAR_API_USER}
- SITAR_API_PASSWORD=${SITAR_API_PASSWORD}
- COVE_FIEL_HASH_KEY=${COVE_FIEL_HASH_KEY}
- COVE_FIEL_HASH_IV=${COVE_FIEL_HASH_IV}
- COVE_API_URL=${COVE_API_URL:-https://api.vu.aduanasoft.com}
- COVE_API_VERIFY_SSL=${COVE_API_VERIFY_SSL:-False}
- CSV_IMPORT_STORAGE=${CSV_IMPORT_STORAGE:-minio}
- S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://minio:9000}
- S3_ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}}
- S3_SECRET_KEY=${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}}
- S3_BUCKET=${S3_BUCKET:-app}
- S3_REGION=${S3_REGION:-us-east-1}
- S3_USE_SSL=${S3_USE_SSL:-false}
- S3_FILE_STORAGE=${S3_FILE_STORAGE:-true}
# Carril hacia EFC (T2026-08-046). El BEAT las necesita porque de aqui salen los tres
# barridos —`sweep_outbox`, `sweep_file_outbox` y `sweep_expediente_gaps`—, que son la red
# que atrapa lo que el despacho inmediato no alcanzo a entregar. Sin ellas el carril
# depende solo del despacho inmediato y una caida de EFC deja la cola detenida para
# siempre. Ver la nota completa en el servicio `backend`.
- EFC_API_URL=${EFC_API_URL:-}
- EFC_API_KEY=${EFC_API_KEY:-}
- EFC_API_VERIFY_SSL=${EFC_API_VERIFY_SSL:-true}
- EFC_API_TIMEOUT_MS=${EFC_API_TIMEOUT_MS:-8000}
- EFC_UPLOAD_TIMEOUT_MS=${EFC_UPLOAD_TIMEOUT_MS:-55000}
- EFC_MTLS_CA_PATH=${EFC_MTLS_CA_PATH:-}
- EFC_MTLS_CERT_PATH=${EFC_MTLS_CERT_PATH:-}
- EFC_MTLS_KEY_PATH=${EFC_MTLS_KEY_PATH:-}
depends_on:
- backend
- valkey
volumes:
- backend_layouts:/app/layouts
networks:
- backend-net
restart: unless-stopped
valkey:
image: valkey/valkey:7.2
container_name: valkey
restart: always
ports:
- "6579:6379"
networks:
- backend-net
minio:
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
container_name: app-minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
ports:
- "${MINIO_API_PORT:-9100}:9000"
- "${MINIO_CONSOLE_PORT:-9101}:9001"
volumes:
- minio_data:/data
networks:
- backend-net
restart: unless-stopped
healthcheck:
test: [ "CMD-SHELL", "curl -f http://127.0.0.1:9000/minio/health/live || exit 1" ]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Frontend - SvelteKit
frontend:
image: dev.aduanasoft.com/anexo76/frontend:latest
container_name: app-frontend
environment:
- NODE_ENV=${NODE_ENV:-production}
- VITE_API_URL=${VITE_API_URL:-https://anexo76-dev.aduanasoft.com/api}
- INTERNAL_API_URL=${INTERNAL_API_URL:-http://backend:8000/api/}
- INTERNAL_HUB_URL=https://workspace.aduanasoft.com
- HUB_URL=https://workspace.aduanasoft.com
# VITE_HUB_URL: URL pública del Hub para el browser (bakeada en build, pero también se lee
# en runtime por $env/dynamic/private en workspace-auth.ts). El root .env puede tener
# localhost; isDevOnlyUrl() lo descarta y cae a HUB_URL (correcto arriba).
- VITE_HUB_URL=https://workspace.aduanasoft.com
# Keycloak — fuente única de autenticación: Workspace (workspace.aduanasoft.com)
# VITE_KEYCLOAK_URL: URL pública que el browser usa para el flujo OIDC.
# Debe ser el mismo Keycloak donde el usuario tiene su sesión de Workspace.
- VITE_KEYCLOAK_URL=${VITE_KEYCLOAK_URL:-https://workspace.aduanasoft.com/kcauth}
- VITE_KEYCLOAK_REALM=${VITE_KEYCLOAK_REALM:-master}
- VITE_KEYCLOAK_CLIENT_ID=${VITE_KEYCLOAK_CLIENT_ID:-app-frontend}
# KEYCLOAK_URL: URL que usa SvelteKit server-side para intercambiar código por tokens.
- KEYCLOAK_URL=${KEYCLOAK_URL:-https://workspace.aduanasoft.com/kcauth}
- KEYCLOAK_REALM=${KEYCLOAK_REALM:-master}
- KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-app-frontend}
- KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-}
# SvelteKit ORIGIN — determina url.origin en load functions y el flag secure de cookies.
# ⚠️ Docker Compose carga el .env raíz automáticamente. Si ese .env tiene
# ORIGIN=http://localhost:5173 (valor dev), sobreescribe el default de abajo.
# SITE_URL es el fallback que usa el código cuando url.origin es localhost.
- ORIGIN=${ORIGIN:-https://anexo76-dev.aduanasoft.com}
# SITE_URL: hardcoded — no depende del .env raíz.
# resolveSystemBaseUrl() lo usa cuando ORIGIN tiene localhost (root .env de dev en prod).
- SITE_URL=https://anexo76-dev.aduanasoft.com
ports:
- "5111:5173"
depends_on:
backend:
condition: service_healthy
networks:
- frontend-net
- backend-net
restart: unless-stopped
command: [ "pnpm", "start" ]
healthcheck:
test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:5173/ || exit 1" ]
interval: 15s
timeout: 5s
retries: 5
start_period: 45s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 1G
reservations:
memory: 512M
volumes:
postgres_app_data:
driver: local
frontend_node_modules:
driver: local
backend_cache:
driver: local
backend_uploads:
driver: local
backend_layouts:
driver: local
minio_data:
driver: local
networks:
backend-net:
driver: bridge
frontend-net:
driver: bridge