Files
CRM_AGENTES_CARGA/.env.example
marcos 02feb973c9 feat(crm): carril con reintentos que entrega los documentos del CRM a EFC
Clon del gateway Anexo22 -> EFC que ya esta en produccion, con los nombres
cambiados. No es una reinterpretacion: la maquina de reintentos de tres capas,
el corte en 4xx y las cuatro guardas de idempotencia se conservan tal cual.

Sin esto, "EFC es la fuente unica" obligaria a llamar a EFC dentro del request
del usuario, y un EFC caido le haria perder su trabajo. Con outbox + Celery, la
subida responde 201 siempre y el sistema entrega cuando EFC vuelve, sin
duplicar: el crm_document_ref viaja con la subida y EFC devuelve 200 con el
documento que ya existia en vez de crear otro. Es lo que cubre el timeout
ambiguo -EFC commiteo y contesto tarde-, donde el CRM no puede saber si entro.

TRES DESVIACIONES DELIBERADAS DEL ORIGINAL, las tres con su razon en el codigo:

1. SAVEPOINT en vez de db.rollback() en el except del encolado. En Anexo22 el
   outbox vive en OTRA base que el pedimento, asi que su rollback solo revertia
   la sesion del outbox. El CRM es mono-base y el encolado corre DENTRO de la
   transaccion del usuario: heredar ese rollback tumbaba la solicitud y el
   expediente recien creados -exactamente lo contrario de best-effort, y en
   silencio-. Lo destapo un test y asi se manifestaba:
   "InvalidRequestError: Instance '<ServiceRequest>' is not persistent within
   this Session". Con el savepoint el fallo deshace solo la fila del outbox.

2. source_table junto a source_id en la guarda _ya_entregado. El CRM tiene DOS
   tablas de documentos con secuencias independientes: crm.documents.id = 5 y
   ops.shipment_documents.id = 5 son documentos distintos. Con el id solo, haber
   entregado el primero haria que el segundo se saltara para siempre sin un solo
   error visible. Hay test que lo fija.

3. EFC_UPLOAD_TIMEOUT_MS aparte de EFC_API_TIMEOUT_MS. Los 8 s de los metadatos
   no alcanzan para un archivo de 25 MB, y el timeout debe quedar POR DEBAJO del
   proxy_read_timeout del nginx de EFC: si el CRM esperara mas, veria un 504
   opaco sin saber si el documento entro.

El cliente HTTP llega con las pruebas que el carril de referencia NO tiene
-verificado: en Anexo22 no hay ni un test de EfcClient._request-, asi que alli
el bucle de reintentos, el backoff y el corte en 4xx nunca se ejercitan. Ese
hueco no se clona: 16 casos contra httpx.MockTransport, sin tocar la red.

Las migraciones se GENERAN y se dejan SIN aplicar.

BLOQUEADO: docker-compose.prod.yml no se toco. El ticket pide las 8 variables en
api, worker y beat, pero Orquestacion.md 13.14 y 4.5 lo prohiben expresamente
("ni tocarlo"), y el orquestador manda sobre el ticket. Queda como paso manual
en el reporte; sin el, worker y beat no ven EFC_API_URL y el carril queda
apagado en produccion, que es degradar limpio y no romper.

Verificacion: pytest tests/ EXIT=0, 151 passed 1 skipped (baseline 70 passed).
Cuatro roturas deliberadas y restauradas: quitando source_table de la guarda el
test de la ambiguedad se puso rojo; reintentando los 4xx los tres tests del
corte dieron "assert 3 == 1"; borrando el objeto local antes de subir cayeron
los tres del corte directo; y disparando el ensure por cualquier 404 se rompio
el test del code.

Ticket: T2026-08-046 (fase 6 de 7)
2026-08-07 18:05:48 -06:00

135 lines
4.7 KiB
Plaintext

# ==================================
# CRM Aduanasoft - Variables de Entorno
# ==================================
# ----- PostgreSQL App -----
POSTGRES_APP_PASSWORD=postgres
# ----- PostgreSQL Keycloak -----
POSTGRES_KEYCLOAK_PASSWORD=postgres
# ----- Keycloak Admin -----
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=admin
# ----- Keycloak Configuración -----
KEYCLOAK_REALM=master
KEYCLOAK_CLIENT_ID=app-backend
KEYCLOAK_CLIENT_SECRET=dev-secret
KEYCLOAK_FRONTEND_CLIENT_ID=app-frontend
# ----- Backend -----
APP_NAME=CRM Aduanasoft
DEBUG=True
ENVIRONMENT=development
CORE_DB_HOST=postgres
CORE_DB_PORT=5432
CORE_DB_NAME=crm_core
CORE_DB_USER=postgres
CORE_DB_PASSWORD=postgres
# Lista de orígenes permitidos (CORS)
# Ejemplo para Hub: http://localhost:5173,http://100.78.6.108:5174,http://100.78.6.108:8001
CORS_ORIGINS=http://localhost:5173,http://localhost:3000
# ----- Frontend -----
NODE_ENV=development
VITE_API_URL=http://localhost:8000/api
INTERNAL_API_URL=http://backend:8000/api
# ⚠️ ADVERTENCIA PRODUCCIÓN — ORIGIN, VITE_HUB_URL y APP_PUBLIC_URL
# ─────────────────────────────────────────────────────────────────────
# Docker Compose carga ESTE archivo (.env raíz) automáticamente cuando
# se ejecuta: docker compose -f docker-compose.prod.yml up
#
# Si estas variables tienen valores localhost aquí, PISARÁN los defaults
# de docker-compose.prod.yml y causarán que el login falle en producción
# (redirect_uri y KC URL apuntarán a localhost).
#
# Para dev local (docker-compose.yml): dejar localhost.
# Para producción (docker-compose.prod.yml): asegurarse que el servidor
# NO tenga este .env raíz con valores localhost, O usar:
# docker compose --env-file .env.prod -f docker-compose.prod.yml up
#
# Variables críticas para producción:
# ORIGIN=https://anexo76-dev.aduanasoft.com ← determina url.origin en SvelteKit
# VITE_HUB_URL=https://workspace.aduanasoft.com
# APP_PUBLIC_URL=https://anexo76-dev.aduanasoft.com
ORIGIN=http://localhost:5173
VITE_HUB_URL=http://localhost:3001
APP_PUBLIC_URL=http://localhost:5173
VITE_KEYCLOAK_REALM=master
VITE_KEYCLOAK_URL=http://localhost:8080/kcauth
VITE_KEYCLOAK_CLIENT_ID=app-frontend
#------ Celery / Valkey ----------
VALKEY_URL=redis://valkey:6379/0
PERMISSION_CACHE_ENABLED=true
PERMISSION_CACHE_TTL_SECONDS=300
# ----- MinIO (S3-compatible) -----
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
MINIO_API_PORT=9100
MINIO_CONSOLE_PORT=9101
# ----- Imports CSV (layouts_csv): redis | minio -----
CSV_IMPORT_STORAGE=minio
S3_ENDPOINT_URL=http://minio:9000
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
S3_BUCKET=crm
S3_REGION=us-east-1
S3_USE_SSL=false
# Logos, certificados, help: mismo bucket. Si CSV_IMPORT_STORAGE=minio, también se usa MinIO aquí
# (use_s3_object_storage = minio CSV o S3_FILE_STORAGE=true).
S3_FILE_STORAGE=true
S3_PRESIGNED_EXPIRES_SECONDS=3600
COVE_FIEL_HASH_KEY=
COVE_FIEL_HASH_IV=
COVE_API_URL=https://api.vu.aduanasoft.com
COVE_API_VERIFY_SSL=False
# ----- Sitar API -----
SITAR_API_URL=http://api.sitar.aduanasoft.com
SITAR_API_USER=user_sitar_api
SITAR_API_PASSWORD=password123
# ==================================
# CONFIGURACIÓN DE SINCRONIZACIÓN
# ==================================
# Hub IP: 100.78.6.108
# URL del Hub para sincronización (Solo si es CLIENTE)
# Ejemplo: http://100.78.6.108:8001/api/v1/core/help-center/sync/
CENTRAL_SERVER_URL=
# UUID único de este cliente (Opcional, se genera uno si está vacío)
# Token de seguridad compartido (Debe ser IDÉNTICO en Hub y Clientes)
SYNC_SECRET_TOKEN=change-this-sync-token-in-production
# Lista de spokes (Solo si es HUB y desea retransmitir a otros - Opcional)
SPOKE_URLS=""
# ── EFC (expediente electronico) ─────────────────────────────────────────────
# Carril CRM -> EFC: los documentos del CRM se resguardan en el expediente de EFC.
# Los nombres son los MISMOS que usa el gateway de Anexo22 contra el mismo EFC.
#
# EFC_API_URL VACIA = integracion APAGADA. Todo el enganche es best-effort y hace no-op:
# el CRM sigue funcionando igual, guardando los archivos solo en su MinIO.
# EFC_API_KEY debe coincidir con CRM_INTEGRATION_API_KEY del lado de EFC.
EFC_API_URL=
EFC_API_KEY=
EFC_API_VERIFY_SSL=true
# Metadatos: resolver organizacion, crear expediente, completar.
EFC_API_TIMEOUT_MS=8000
# Subidas. Debe quedar POR DEBAJO del proxy_read_timeout del nginx de EFC: si el CRM
# esperara mas, veria un 504 opaco y no sabria si el documento entro.
EFC_UPLOAD_TIMEOUT_MS=55000
# Scaffolding de mTLS (pre-produccion). Vacio = TLS normal.
EFC_MTLS_CA_PATH=
EFC_MTLS_CERT_PATH=
EFC_MTLS_KEY_PATH=