Files
shopify_run_run/docs/casos_especiales.md
T
georgeandClaude Sonnet 5.5 901dd8763d Integración Shopify → SAP Business One (snobbycl y on-chile)
Pipeline de integración de órdenes de Shopify a SAP Service Layer:
Invoice de reserva, pago con tarjeta o transferencia por transacción,
solicitud de traslado, traslado de stock y entrega.

- Invoice igual a lo cobrado; fulfillments limitados a lo facturado
- Estados terminales integrated, timeout, ignored y cancelled
- Idempotencia contra SAP: adopta documentos existentes en vez de duplicar
- Reintentos de red en el cliente de Shopify
- Secretos fuera de los JSON de tienda (${VARIABLE} resuelta desde .env)
- Herramientas de consulta, integración puntual y carga de pruebas
- Documentación en docs/ (flujo, casos especiales, mapeo, configuración)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 19:22:40 -05:00

11 KiB

Casos especiales de integración (Shopify → SAP)

Casos que se salen del flujo normal (docs/flujo_pipeline.md): cómo se detectan, qué hace el pipeline y una orden real de ejemplo por tienda. "sin caso" significa que no se encontró un ejemplo real en esa tienda (no que no pueda ocurrir). Los JSON de Shopify de la mayoría de los ejemplos están en download/ (shopify_#<name>.json = orden, shopify_transactions_#<name>.json = transacciones).

Tiendas: snobbycl (órdenes #NNNN) y on-chile-distributor-prod (órdenes #CLNNNN).

Regla general de montos

La Invoice siempre iguala lo realmente cobrado en Shopify (suma de las transacciones efectivas). Las líneas se arman en domain/invoice.py::_lineas_por_cobrado:

  1. Si el total de las líneas originales (quantity) coincide con lo cobrado → se usan esas.
  2. Si no, y el total de las líneas vigentes (current_quantity) coincide → se usan esas.
  3. Si ninguna coincide → se usan las originales y el control de suma del pago deja la orden en error (no se crea el pago).

Transacciones efectivas (domain/payment.py::transacciones_efectivas): solo status = success y kind sale o capture. Si una authorization tiene su capture (parent_id), cuenta solo la capture. Se ignoran failure, pending, refund y void.


A. Pagos

# Caso Cómo se detecta Tratamiento snobbycl on-chile
A1 Venta normal una sale exitosa = total de la orden Invoice + pago + ITR (+ ST + Delivery si es tienda) #8064 #CL1467
A2 Intentos fallidos o pendientes antes del pago exitoso varias transacciones, solo una success. payment_gateway_names lista todos los intentos y no indica quién pagó solo la exitosa suma #7880 (Transbank failure → Fintoc ok), #7903 (Mercado Pago pending → Transbank ok), #8063 (4 intentos, uno con status error, y 1 éxito) #CL9958, #CL9994, #CL10041 (36 en septiembre)
A3 authorization + capture (reversso) dos success, la capture con parent_id = la authorization solo cuenta la capture; reversso va como transferencia (1-1-010-20-000) #7902 #CL9948, #CL9950, #CL9969 (46 en septiembre)
A4 Pago dividido real dos sale success de gateways distintos una línea de PaymentCreditCards por transacción, cada una con su tarjeta sin caso #CL10236 (Fintoc 152.991 + Transbank 54.000), #CL10594, #CL10967; #CL10947 (Transbank + Transbank Webpay, misma tarjeta SAP 4 → dos líneas)
A5 Gateway con nombre distinto al configurado el gateway no coincide con la clave de sap.payment_methods (comparación exacta) agregar las variantes al config con la misma tarjeta sin caso (sus nombres coinciden) Venti (#CL9933, #CL10054) y Transbank (#CL10236), equivalentes a VentiPay y Transbank Webpay
A6 Gateway sin configurar no está en sap.payment_methods, o sin credit_card, o type inválido PaymentConfigError: el documento de pago queda en error con el mensaje y no se crea el pago (la Invoice ya existe y se retoma después) prueba sintética con un gateway inexistente prueba sintética
A7 VoucherNum SAP rechaza más de 20 caracteres (Value too long in property 'VoucherNum') VoucherNum = name de la orden (máx. 20) — #CL1467 (falló con el payment_id de 25 caracteres)

Datos fijos de las tarjetas (domain/payment.py, provisorios): últimos dígitos 1234, vencimiento 2030-12-31, CreditType cr_Regular; SplitPayments y NumOfPayments según el config de cada medio.

B. Líneas canceladas, devueltas o reembolsadas

# Caso Cómo se detecta Tratamiento snobbycl on-chile
B1 Línea cancelada antes del pago refund_line_items con restock_type = cancel; cobrado = total neto (current_total_price) la Invoice usa las líneas vigentes #7998 (4 unidades, cobrado 339.980; Invoice original DocNum 460 anulada y reintegrada como DocNum 516) no detectado en septiembre
B2 Línea cancelada con dinero reembolsado después de cobrar el total cancel + transacción refund exitosa; cobrado = total original la Invoice usa las líneas originales (el reembolso es un tratamiento interno de comercial) sin caso #CL10072 (cobrado 305.973, reembolsado 71.991)
B3 Línea devuelta (return) sin cobro restock_type = return, sin transacción refund, cobrado = total vigente la Invoice usa las líneas vigentes y fulfillments_facturados limita los fulfillments a lo facturado: no se hace ITR, ST ni Delivery de la unidad devuelta (el Delivery fallaría con KeyError al no existir la línea base) sin caso 15 órdenes, entre ellas #CL10144, #CL10764, #CL10903
B4 Despachada, pagada y luego reembolsada (total o parcial) financial_status refunded o partially_refunded, con fulfillments y sin cancelled_at se integra completa como venta; el reembolso no se refleja en SAP (ESTADOS_FINANCIEROS_FACTURABLES) #7954, #7963, #7978, #7983, #7987, #8020, #8029 (parcial) #CL9947, #CL9968, #CL9994, #CL9998, #CL10039, #CL10047 (23 en septiembre, aún sin integrar)
B5 Reembolso de dinero pendiente, sin líneas refund en pending y refunds sin refund_line_items, total vigente = cobrado no afecta Invoice ni pago: se integra normal #8009 sin caso
B6 Pedido de prueba decisión manual status = ignored (terminal) #8019 sin caso

C. Estado de la orden en Shopify

# Caso Cómo se detecta Tratamiento snobbycl on-chile
C1 Orden anulada cancelled_at no vacío, aunque siga paid status = cancelled (terminal) con fecha y cancel_reason en last_error; evento order_cancelled; nunca se crean documentos #7893, #7985, #8003, #7862 (14 en total) #CL9983, #CL10015, #CL10161 (22 en septiembre)
C2 Pago pendiente o no pagada financial_status fuera de paid/partially_refunded/refunded (ej. pending, voided) not_eligible; se reevalúa en cada ciclo sin caso vigente (#8010 y #8021 eran voided y están anuladas) #CL10283, #CL10733, #CL10828 (pending)
C3 Pagada y sin despachar sin fulfillments not_eligible hasta que se despache; al despacharse se integra sola #8055 #CL9951, #CL10006 (73 en septiembre)
C4 Costo de envío mayor que cero total_shipping_price_set > 0 error: el mapeo del envío no está soportado sin caso #CL10169, #CL1452, #CL1453
C5 Orden sin líneas vigentes todas las líneas canceladas not_eligible ("Orden sin líneas vigentes") sin caso sin caso

Estados terminales: integrated, timeout, ignored, cancelled (ver docs/esquema_base_datos.md).

D. SAP

# Caso Síntoma Tratamiento snobbycl on-chile
D1 SKU inexistente POST Invoices 400 Item number is missing (-5009) crear los artículos (tools/cargar_items_faltantes.py, grupo 108); la orden se reintenta #8064 (primer intento) detectado antes de integrar: 874 artículos faltantes en las órdenes de septiembre
D2 Sin stock en la bodega de origen el ITR/transferencia es rechazado entrada de mercancía (tools/entrada_mercancia_ordenes.py) bodegas 8801, 8802 bodegas 8803, 7702
D3 Anular una Invoice Cancellation is not possible; posting date exceeds limit of 10 days. El DocNum no es el DocEntry reintentar cuando SAP lo permita; anular siempre por DocEntry #7998 (DocNum 460 = DocEntry 514) —
D4 Documento base cerrado Delivery: One of the base documents has already been closed error en cada ciclo hasta resolverlo en SAP #7863 (antiguo) —
D5 Cliente con RUT note_attributes con un RUT válido se busca/crea el BusinessPartner C<RUT> (crea clientes reales en SAP) sin caso (ninguna orden trae RUT) #CL1467 → C<RUT> (ej. C12345678-5) (977 de 1.049 elegibles traen RUT)
D6 Cliente sin RUT sin RUT válido en note_attributes cliente genérico sap.generic_card_code (C99998); no se consultan los metafields del customer #8064 #CL9948

E. Datos, cursor y reprocesos

# Caso Descripción Tratamiento snobbycl on-chile
E1 Cursor since_id se descarga desde max(id máximo en BD, initial_since_id); initial_since_id es un piso. Órdenes de prueba con ids altos en la BD hacían saltar el cursor alinear la BD (insertar cabeceras) o borrar las de prueba — #CL10809 y #CL10851 saltaban el cursor a fines de septiembre
E2 Orden integrada en SAP sin registro local si se borra su fila y vuelve a descargarse, se integraría de nuevo reinsertarla como ignored; además la idempotencia (E6) adopta los documentos existentes — #CL10809 (Invoice 319, pago 67, ITR 23)
E3 Timeout timeout_days (60) desde la creación en Shopify timeout (terminal). Corte operativo de on-chile: 01/09/2026 (initial_since_id = 8103221690695) — órdenes de septiembre en adelante
E4 API de Shopify caída error 401/403/red el error se propaga: el ciclo registra "Error procesando tienda" y reintenta en el siguiente. Ya no hay fallback con archivos manuales (se retiró; se usó mientras faltaba el scope read_orders de on-chile) — —
E5 Casos para análisis el pipeline no guarda solo los casos especiales tools/descargar_ordenes_especiales.py <store> los baja a download/ 26 órdenes 55 órdenes
E6 Documento ya existente en SAP (idempotencia) la BD local no registra un documento que sí existe en SAP (BD perdida, copiada o editada) antes de crear Invoice, ITR, StockTransfer o Delivery se busca por U_ShopifyOrderId + U_ShopifyStore con las mismas líneas y se adopta; solo Invoices vigentes (CancelStatus = csNo); una Invoice ya pagada no genera otro pago; evento documento_adoptado. Límite: no hay respaldo para el pago si la Invoice no figura pagada #8064, #7998 (adopta la Invoice 516, no la anulada 460) #CL1467
E7 Corte de red SSLEOFError, reset de conexión, timeout ShopifyClient reintenta hasta 4 veces con espera creciente; los errores HTTP no se reintentan — corte SSL al paginar cabeceras

F. No cubierto todavía

  • Reembolso o anulación posterior a integrar: integrated es terminal, por lo que SAP queda con la venta completa y sin nota de crédito ni devolución. Decisión pendiente con contabilidad (docs/pendientes.md).
  • Fulfillments vs. Invoice: fulfillments_facturados reparte las cantidades de la Invoice entre los fulfillments en orden; si una misma línea se devolviera parcialmente entre varios fulfillments, el reparto sigue ese orden y no el real.
  • Transferencias a cuentas distintas: un solo TransferAccount por pago; con dos cuentas el pago da error.

Herramientas útiles

Herramienta Uso
python -m tools.consultar resumen / descargadas / integradas / errores / orden --name "#8064" estado de la BD (solo lectura, hora local; APP_TIMEZONE)
python -m tools.integrar_ordenes <store> <shopify_id> [--apply] dry-run o integración de órdenes puntuales
python -m tools.probar_pago <store> <shopify_id> payload del pago sobre la Invoice real
python -m tools.descargar_ordenes_especiales <store> guarda casos especiales en download/
python -m tools.entrada_mercancia_ordenes <config> --warehouse ... --orders ... stock para los SKUs de órdenes puntuales