Mejoras de sesion: estabilizacion, IA, Nexus Yonke y marketplace B2B
- F0 blindaje: Redis/Celery, pg_hba sin trust LAN, secretos a env 600, backups, webhook auth, jobs muertos - F1 caja: bugs POS, reembolsos al corte, apartados, CFDI, topes de descuento en cotizacion/apartado - F2/F7 IA de catalogo: autocategorizacion, dedup de SKUs con embeddings, OCR de facturas, descripciones, claves SAT, limpieza de fitment - F3 IA en el flujo: vision Yonke, preguntas MeLi, venta cruzada, anomalias, sinonimos, busqueda semantica (rerank/embeddings) - F4/F8 escala y cartera: /metrics+Prometheus+Grafana, pool, score de credito, churn, metricas multi-tenant - Nexus Yonke (M1-M6): modelo de datos 1-de-1, alta por donador, vitrina publica, MeLi usado, consignacion, garantia por dias, reportes, etiquetas - Marketplace B2B entre yonkes (catalogo + pedidos cross-tenant) - Seguridad: auth en /api/admin/* (35 endpoints), alertas por tenant - Migraciones v4.27/v4.28; timers expire-demos y rollup-metrics
This commit is contained in:
@@ -108,6 +108,18 @@ def _get_user_key() -> str | None:
|
||||
return USER_KEY.strip() or None
|
||||
|
||||
|
||||
def _find_user_key(tenant_config: dict) -> str | None:
|
||||
"""Return the Facturapi user key (env or sk_user_* tenant key), if any."""
|
||||
user_key = _get_user_key()
|
||||
if user_key:
|
||||
return user_key
|
||||
for key in ("facturapi_key", "cfdi_facturapi_key"):
|
||||
tenant_key = (tenant_config.get(key) or "").strip()
|
||||
if tenant_key.startswith("sk_user_"):
|
||||
return tenant_key
|
||||
return None
|
||||
|
||||
|
||||
def _is_user_key_mode(tenant_config: dict) -> bool:
|
||||
return bool(_get_user_key()) and not _get_secret_key(tenant_config)
|
||||
|
||||
@@ -138,7 +150,9 @@ def get_organization(org_id: str, api_key: str) -> dict:
|
||||
def upload_csd(tenant_config: dict, cer_b64: str, key_b64: str, password: str) -> dict:
|
||||
"""Upload CSD (Certificado de Sello Digital) to Facturapi.
|
||||
|
||||
cer_b64 and key_b64 are base64-encoded strings.
|
||||
cer_b64 and key_b64 are base64-encoded strings. Uses the documented
|
||||
PUT /organizations/{id}/certificate endpoint with the `cer`, `key` and
|
||||
`password` multipart fields.
|
||||
"""
|
||||
api_key = get_api_key(tenant_config)
|
||||
org_id = _get_org_id(tenant_config)
|
||||
@@ -150,11 +164,11 @@ def upload_csd(tenant_config: dict, cer_b64: str, key_b64: str, password: str) -
|
||||
|
||||
url = f"{BASE_URL}/organizations/{org_id}/certificate"
|
||||
files = {
|
||||
"certificate": ("certificate.cer", cer_bytes, "application/octet-stream"),
|
||||
"private_key": ("private_key.key", key_bytes, "application/octet-stream"),
|
||||
"secret": (None, password),
|
||||
"cer": ("certificate.cer", cer_bytes, "application/octet-stream"),
|
||||
"key": ("private_key.key", key_bytes, "application/octet-stream"),
|
||||
"password": (None, password),
|
||||
}
|
||||
resp = requests.post(url, auth=(api_key, ""), files=files, timeout=60)
|
||||
resp = requests.put(url, auth=(api_key, ""), files=files, timeout=60)
|
||||
if not resp.ok:
|
||||
raise FacturapiError(
|
||||
f"CSD upload failed: {resp.status_code} {resp.text[:500]}",
|
||||
@@ -170,13 +184,9 @@ def _get_user_key_for_tenant(tenant_config: dict) -> str:
|
||||
1. FACTURAPI_USER_KEY environment variable
|
||||
2. tenant_config.facturapi_key if it starts with sk_user_
|
||||
"""
|
||||
user_key = _get_user_key()
|
||||
user_key = _find_user_key(tenant_config)
|
||||
if user_key:
|
||||
return user_key
|
||||
for key in ("facturapi_key", "cfdi_facturapi_key"):
|
||||
tenant_key = (tenant_config.get(key) or "").strip()
|
||||
if tenant_key.startswith("sk_user_"):
|
||||
return tenant_key
|
||||
raise FacturapiError("FACTURAPI_USER_KEY env or a Facturapi user key (sk_user_*) is required")
|
||||
|
||||
|
||||
@@ -347,7 +357,7 @@ def get_org_status(tenant_config: dict) -> dict:
|
||||
}
|
||||
|
||||
has_secret = bool(_get_secret_key(tenant_config))
|
||||
has_user = bool(_get_user_key_for_tenant(tenant_config))
|
||||
has_user = bool(_find_user_key(tenant_config))
|
||||
if not has_secret and not has_user:
|
||||
result["error"] = "Facturapi not configured. Set FACTURAPI_USER_KEY env or tenant_config.facturapi_secret_key"
|
||||
return result
|
||||
@@ -366,10 +376,10 @@ def get_org_status(tenant_config: dict) -> dict:
|
||||
result["error"] = "No Facturapi key available"
|
||||
return result
|
||||
|
||||
def _fetch():
|
||||
org = get_organization(org_id, api_key)
|
||||
legal = org.get("legal", {})
|
||||
cert = org.get("certificate", {})
|
||||
def _fetch(key=None):
|
||||
org = get_organization(org_id, key or api_key)
|
||||
legal = org.get("legal") or {}
|
||||
cert = org.get("certificate") or {}
|
||||
return {
|
||||
"configured": True,
|
||||
"has_csd": bool(cert.get("has_certificate")),
|
||||
@@ -383,10 +393,11 @@ def get_org_status(tenant_config: dict) -> dict:
|
||||
except FacturapiError as e:
|
||||
# If the org reports "not configured for Live" (401) and we have a user key,
|
||||
# try to backfill legal data and retry.
|
||||
if e.status_code == 401 and _get_user_key_for_tenant(tenant_config):
|
||||
user_key = _find_user_key(tenant_config)
|
||||
if e.status_code == 401 and user_key:
|
||||
try:
|
||||
update_organization_legal(tenant_config, org_id)
|
||||
result.update(_fetch())
|
||||
result.update(_fetch(user_key))
|
||||
except FacturapiError as e2:
|
||||
result["error"] = str(e2)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user