Estado: ✅ Sprint 1 COMPLETADO (3 Nov 2025) - Sistema 100% funcional
Stack: InterSystems IRIS 2025.1 + MySQL 8.0 + Java Gateway + Interoperability Production
Sistema end-to-end de recuperación de carritos abandonados con:
- ✅ Webhook REST API - Recibe notificaciones de VTEX
- ✅ Consulta de inventario REAL - MySQL vía JDBC con stock disponible y variantes alternativas
- ✅ Generación de emails - Archivos .txt personalizados con alternatives
- ✅ Auditoría completa - Persistencia en clase
ECOM.Data.Auditcon 35+ registros - ✅ Business Process orchestration - BPL con validación, iteración, error handling
Sistema incluye arquitectura completa: REST API → Business Service → Business Process (BPL) → Business Operations (MySQL + Email + Storage) con traces y logging estructurado.
- Docker Desktop
- InterSystems IRIS 2025.1 en container
iris102 - MySQL 8.0 en container
iris102-mysql - curl o Postman para testing
# 1. Verificar que la Production está activa
curl http://localhost:52773/csp/ecommerce/health -u "SuperUser:123"
# 2. Enviar carrito abandonado
curl -X POST http://localhost:52773/csp/ecommerce/vtex/abandoned \
-H "Content-Type: application/json" \
-u "SuperUser:123" \
-d '{
"cartId": "TEST-001",
"customerEmail": "test@example.com",
"customerName": "Test User",
"items": [
{
"sku": "SKU-001",
"size": "M",
"color": "BLACK",
"qty": 1
}
]
}'
# 3. Verificar email generado
docker exec iris102 ls -lh /home/irisowner/data/emails/
# 4. Ver contenido del email
docker exec iris102 cat /home/irisowner/data/emails/email_TEST-001_*.txt
# 5. Verificar audit record
# Acceder a Portal: http://localhost:52773/csp/sys/UtilHome.csp
# SQL: SELECT * FROM ECOM_Data.Audit WHERE CartId='TEST-001'- Namespace:
ECOMMERCE(Interoperability habilitado)
Class ECOM.Msg.CartAbandonedRequest Extends Ens.Request
{
Property CartId As %String(MAXLEN=100);
Property CustomerEmail As %String(MAXLEN=200);
Property CustomerName As %String(MAXLEN=120);
Property Items As %DynamicArray; // [{"sku":"SKU-001","qty":1,"size":"M","color":"Black"}, ...]
Property LastUpdate As %TimeStamp;
}
Class ECOM.Msg.CartAbandonedResponse Extends Ens.Response
{
Property Accepted As %Boolean;
Property Reason As %String(MAXLEN=512);
}Mensajes para consulta a SAP (JDBC) — Disponibilidad y variantes
Class ECOM.Msg.SAPCheckRequest Extends Ens.Request
{
Property CartId As %String;
Property Material As %String; // material base SAP
Property Size As %String; // talla
Property Color As %String; // color
Property Qty As %Integer;
}
Class ECOM.Msg.SAPCheckResponse Extends Ens.Response
{
Property CartId As %String;
Property CurrentStock As %Integer; // stock para talla/color solicitados
Property Variants As %DynamicArray; // alternativas: [{"size":"L","color":"Black","stock":7}, ...]
}Mensajes para CRM Email
Class ECOM.Msg.CRMEmailRequest Extends Ens.Request
{
Property ToEmail As %String;
Property Template As %String; // e.g. "ABANDONED_CART_V1"
Property Tokens As %DynamicObject; // { name, productName, original:{size,color}, alternatives:[...], deeplink }
}
Class ECOM.Msg.CRMEmailResponse Extends Ens.Response
{
Property Status As %String; // OK/ERROR
Property MessageId As %String;
Property ErrorMsg As %String(MAXLEN=2000);
}Mensajes genéricos Call (para callouts sincrónicos simulados)
Class ECOM.Msg.CallRequest Extends Ens.Request
{
Property Action As %String; // "SAP.CHECK" | "CRM.SEND" | ...
Property Payload As %DynamicObject;
Property Timeout As %Integer [ InitialExpression = 15 ];
}
Class ECOM.Msg.CallResponse Extends Ens.Response
{
Property Status As %String; // OK/ERROR/TIMEOUT
Property Code As %Integer;
Property Message As %String(MAXLEN=2000);
Property Raw As %DynamicObject;
}A) Webhook HTTP (recomendado)
Class ECOM.BS.VTEXWebhook Extends Ens.BusinessService
{
Parameter ADAPTER = "EnsLib.HTTP.InboundAdapter";
Property URLMap As %String [ InitialExpression = "/vtex/abandoned" ];
Method OnProcessInput(pInput As %Stream.Object, Output pResponse As Ens.Response) As %Status
{
#dim sc As %Status = $$$OK
Try {
Set tBody = pInput.Read()
Set tDyn = {}.%FromJSON(tBody)
Set req = ##class(ECOM.Msg.CartAbandonedRequest).%New()
Set req.CartId = tDyn.cartId
Set req.CustomerEmail = tDyn.customer.email
Set req.CustomerName = $Get(tDyn.customer.name)
Set req.Items = tDyn.items
Set req.LastUpdate = $ZDT($H,3)
Set sc = ..SendRequestAsync("ECOM.BP.CartRecovery", req)
} Catch ex { Set sc = ex.AsStatus() }
Quit sc
}
}B) Poller (batch cada 30 min) — opcional
Class ECOM.BS.VTEXPoller Extends Ens.BusinessService
{
Parameter ADAPTER = "EnsLib.ScheduleHandler"; // o EnsLib.File.InboundAdapter si lees JSONs
Property EveryMinutes As %Integer [ InitialExpression = 30 ];
Method OnTask() As %Status
{
// Simulación: leer archivo o generar payloads de prueba
Set req = ##class(ECOM.Msg.CartAbandonedRequest).%New()
Set req.CartId=$SYSTEM.Util.CreateGUID()
Set req.CustomerEmail="demo@cliente.cl"
Set req.CustomerName="Demo Cliente"
Set req.Items=[{"sku":"SKU-001","qty":1,"size":"M","color":"Black"}]
Set req.LastUpdate=$ZDT($H,3)
Quit ..SendRequestAsync("ECOM.BP.CartRecovery", req)
}
}Class ECOM.BP.CartRecovery Extends Ens.BusinessProcessBPL
{
XData BPMDefinition [ XMLNamespace = "http://www.intersystems.com/bpl" ]
{
<process language="objectscript" requestClass="ECOM.Msg.CartAbandonedRequest" responseClass="ECOM.Msg.CartAbandonedResponse">
<sequence>
<!-- 1) Validación básica -->
<code>
Set tSC = $$$OK
If (request.CartId="")!(request.CustomerEmail="") { Set tSC=$$$ERROR($$$GeneralError,"Missing CartId/Email") }
</code>
<if condition="($IsOK(tSC)=0)"><throw statusVar="tSC"/></if>
<!-- 2) Fan-out: por cada item consultar SAP (simulado o real) -->
<foreach property="request.Items" keyVar="idx" valueVar="it">
<assign property="tReqSAP.CartId" value="request.CartId"/>
<assign property="tReqSAP.Material" value="it.sku"/>
<assign property="tReqSAP.Size" value="it.size"/>
<assign property="tReqSAP.Color" value="it.color"/>
<assign property="tReqSAP.Qty" value="it.qty"/>
<call target="ECOM.BO.SAPInventory" async="false" requestClass="ECOM.Msg.SAPCheckRequest" responseVar="tRespSAP">
<request>
<assign property="CartId" value="tReqSAP.CartId"/>
<assign property="Material" value="tReqSAP.Material"/>
<assign property="Size" value="tReqSAP.Size"/>
<assign property="Color" value="tReqSAP.Color"/>
<assign property="Qty" value="tReqSAP.Qty"/>
</request>
</call>
<code>
// Agregar a una lista acumulada de alternativas por ítem
If '$IsObject(AltList) Set AltList = ##class(%DynamicArray).%New()
Do AltList.%Push(tRespSAP.Variants)
</code>
</foreach>
<!-- 3) Construir deeplink y payload de email -->
<code>
Set dl = "https://shop.example.com/restore?cart="_request.CartId
Set tokens = {}.%New()
Do tokens.%Set("name", request.CustomerName)
Do tokens.%Set("deeplink", dl)
Do tokens.%Set("alternatives", AltList)
// tomar el primer producto como referencia
Set first = request.Items.%Get(0)
Do tokens.%Set("productName", first.sku)
Do tokens.%Set("original", {"size":first.size, "color":first.color})
</code>
<!-- 4) Enviar email por CRM (simulado) -->
<call target="ECOM.BO.CRMEmail" async="false" requestClass="ECOM.Msg.CRMEmailRequest" responseVar="tRespCRM">
<request>
<assign property="ToEmail" value="request.CustomerEmail"/>
<assign property="Template" value="'ABANDONED_CART_V1'"/>
<assign property="Tokens" value="tokens"/>
</request>
</call>
<!-- 5) Auditoría básica -->
<call target="ECOM.BO.Storage" async="true" requestClass="ECOM.Msg.CallRequest">
<request>
<assign property="Action" value="'AUDIT'"/>
<assign property="Payload" value="{""cartId"":request.CartId, ""emailStatus"":tRespCRM.Status}"/>
</request>
</call>
<!-- 6) Respuesta del BP -->
<assign property="response.Accepted" value="($Select(tRespCRM.Status=""OK"":1,1:0))"/>
<assign property="response.Reason" value="($Select(tRespCRM.Status=""OK"":"Sent",""Failed to send email""))"/>
</sequence>
</process>
}
}A) SAP Inventory (simulado) — lee una “vista” local o datos embebidos
Class ECOM.BO.SAPInventory Extends Ens.BusinessOperation
{
Parameter INVOCATION = "Queue";
Property UseMock As %Boolean [ InitialExpression = 1 ];
Method OnMessage(pReq As ECOM.Msg.SAPCheckRequest, Output pResp As ECOM.Msg.SAPCheckResponse) As %Status
{
Set sc=$$$OK, pResp=##class(ECOM.Msg.SAPCheckResponse).%New()
Set pResp.CartId=pReq.CartId
Try {
If ..UseMock {
// Mock: stock 0 para talla exacta; ofrecer variantes
Set pResp.CurrentStock = 0
Set alts = ##class(%DynamicArray).%New()
Do alts.%Push({"size":"L","color":pReq.Color,"stock":7})
Do alts.%Push({"size":pReq.Size,"color":"Navy","stock":3})
Do alts.%Push({"size":"S","color":pReq.Color,"stock":2})
Set pResp.Variants = alts
} Else {
// Real JDBC: SELECT a la vista ATP (pseudocódigo)
// Use EnsLib.SQL.OutboundAdapter o %SQL.Statement
// SELECT stock FROM V_ATP WHERE material=?, size=?, color=?
}
} Catch ex { Set sc=ex.AsStatus() }
Quit sc
}
}B) CRM Email (simulado)
Class ECOM.BO.CRMEmail Extends Ens.BusinessOperation
{
Parameter INVOCATION = "Queue";
Property UseMock As %Boolean [ InitialExpression = 1 ];
Method OnMessage(pReq As ECOM.Msg.CRMEmailRequest, Output pResp As ECOM.Msg.CRMEmailResponse) As %Status
{
Set sc=$$$OK, pResp=##class(ECOM.Msg.CRMEmailResponse).%New()
Try {
If ..UseMock {
Set pResp.Status="OK", pResp.MessageId=$SYSTEM.Util.CreateGUID()
} Else {
// Real: EnsLib.HTTP.OutboundAdapter -> POST /email
}
} Catch ex { Set sc=ex.AsStatus(), pResp.Status="ERROR", pResp.ErrorMsg=$ZStatus }
Quit sc
}
}C) Storage/Auditoría mínima
Class ECOM.BO.Storage Extends Ens.BusinessOperation
{
Parameter INVOCATION = "Queue";
ClassMethod EnsureTables()
{
// Tablas simples para auditoría
&sql(CREATE TABLE IF NOT EXISTS CartAudit(
CartId VARCHAR(100),
Email VARCHAR(200),
Status VARCHAR(20),
TS TIMESTAMP
))
}
Method OnMessage(pReq As ECOM.Msg.CallRequest, Output pResp As ECOM.Msg.CallResponse) As %Status
{
Set sc=$$$OK, pResp=##class(ECOM.Msg.CallResponse).%New()
Try {
If pReq.Action="AUDIT" {
Set cartId = pReq.Payload.cartId
Set status = pReq.Payload.emailStatus
&sql(INSERT INTO CartAudit(CartId,Email,Status,TS) VALUES(?,?,?,?,?)) // simplificado: ajusta columnas
}
Set pResp.Status="OK", pResp.Code=200
} Catch ex { Set sc=ex.AsStatus(), pResp.Status="ERROR", pResp.Message=$ZStatus }
Quit sc
}
}Nota: La sentencia SQL de
INSERTarriba es un placeholder: adapta columnas y parámetros segúnPayload. Alternativamente, persiste en una clase persistenteECOM.Audit.CartAudit.
Class ECOM.Production Extends Ens.Production
{
XData ProductionDefinition
{
<Production Name="ECOM.Production">
<Description>Abandoned Cart PoC</Description>
<!-- Services -->
<Item Name="BS.VTEXWebhook" ClassName="ECOM.BS.VTEXWebhook" PoolSize="1" Enabled="true">
<Setting Target="Adapter" Name="URLMap" Value="/vtex/abandoned"/>
</Item>
<Item Name="BS.VTEXPoller" ClassName="ECOM.BS.VTEXPoller" PoolSize="1" Enabled="false"/>
<!-- Process -->
<Item Name="BP.CartRecovery" ClassName="ECOM.BP.CartRecovery" PoolSize="1" Enabled="true"/>
<!-- Operations -->
<Item Name="BO.SAPInventory" ClassName="ECOM.BO.SAPInventory" PoolSize="2" Enabled="true"/>
<Item Name="BO.CRMEmail" ClassName="ECOM.BO.CRMEmail" PoolSize="2" Enabled="true"/>
<Item Name="BO.Storage" ClassName="ECOM.BO.Storage" PoolSize="1" Enabled="true"/>
</Production>
}
}{
"cartId": "c_123",
"customer": { "email": "ana@cliente.cl", "name": "Ana" },
"items": [
{ "sku": "SKU-001", "qty": 1, "size": "M", "color": "Black" }
],
"lastUpdate": "2025-10-28T12:00:00Z"
}cURL (si usas HTTP Inbound):
curl -X POST "http://localhost:52773/csp/ecommerce/vtex/abandoned" \
-H "Content-Type: application/json" \
-d @payload.jsonAjusta path según tu aplicación web/dispatch class.
ZN "ECOMMERCE"
Do ##class(Ens.Director).StopProduction()
Set sc=##class(Ens.Config.Production).CreateProductionFromClass("ECOM.Production")
Write $SYSTEM.Status.DisplayError(sc)
Do ##class(Ens.Director).StartProduction()- (Opcional) Ejecuta
Do ##class(ECOM.BO.Storage).EnsureTables()en la Terminal para crear tablas. - Envía el cURL del payload para recorrer el flujo.
- Logs estructurados: usa
$$$LOGINFO/$$$LOGERRORen BOs para registrarCartId, tiempos y resultados. - Métricas: crea un BO ligero
ECOM.BO.Metricspara contar procesados y errores (almacenado en clase persistente o global). Llama de forma asíncrona desde el BPL.
ECOM.BO.SAPInventory.UseMock=0y configurar EnsLib.SQL.OutboundAdapter o%SQL.Statementhacia la vista JDBC en SAP (solo lectura).ECOM.BO.CRMEmail.UseMock=0e implementar EnsLib.HTTP.OutboundAdapter al endpoint transaccional del CRM.
- Idempotencia por
CartIdsi el webhook se repite. - Deeplink incluye
carty (opcional)skude la alternativa sugerida. - Privacidad: enviar solo
emaily datos mínimos necesarios al CRM. - Alertas mínimas: si
CRMEmaildevuelveERROR, generarEns.Alert.
Acceder al terminal de IRIS:
# Desde el host
docker exec -it iris102 iris session IRIS -U ECOMMERCEConsultar logs de eventos:
-- Ver últimos 20 errores de todos los componentes
SELECT TOP 20 TimeLogged, ConfigName, Type, Text
FROM Ens_Util.Log
WHERE Type = 2 -- Type 2 = Error
ORDER BY ID DESC
-- Ver logs de un componente específico
SELECT TOP 10 TimeLogged, Type, Text
FROM Ens_Util.Log
WHERE ConfigName = 'BP.CartRecovery'
ORDER BY ID DESC
-- Buscar logs por CartId o SessionId
SELECT TimeLogged, ConfigName, Text
FROM Ens_Util.Log
WHERE Text LIKE '%test_017_COMPLETE%'
ORDER BY ID DESC
-- Ver logs de todos los componentes del flujo
SELECT TimeLogged, ConfigName, Type, SUBSTRING(Text,1,100) AS Message
FROM Ens_Util.Log
WHERE ConfigName IN ('BP.CartRecovery','BO.SAPInventory','BO.CRMEmail','BO.Storage')
ORDER BY ID DESCTipos de Log:
Type = 1: InfoType = 2: ErrorType = 3: WarningType = 4: Alert
Consultar mensajes en la cola:
// Ver todos los headers de mensajes recientes
Set rs = ##class(%ResultSet).%New("%DynamicQuery:SQL")
Set query = "SELECT TOP 20 ID, SessionId, TimeCreated, SourceConfigName, TargetConfigName, MessageBodyClassName FROM Ens.MessageHeader ORDER BY ID DESC"
Do rs.Prepare(query)
Do rs.Execute()
While rs.Next() {
Write "ID: ", rs.Data("ID"), " | Session: ", rs.Data("SessionId"), !
Write "From: ", rs.Data("SourceConfigName"), " -> To: ", rs.Data("TargetConfigName"), !
Write "Body: ", rs.Data("MessageBodyClassName"), !
Write "---", !
}Ver contenido de un mensaje específico:
// Por ID de mensaje
Set header = ##class(Ens.MessageHeader).%OpenId(123)
If $IsObject(header) {
Set body = ##class(Ens.MessageBody).%OpenId(header.MessageBodyId)
zwrite body
}
// Por SessionId (ver todo el flujo)
Set query = "SELECT ID FROM Ens.MessageHeader WHERE SessionId = 528740"
// Luego abrir cada mensajeConsultar cola de un componente:
// Ver mensajes pendientes en una cola
Do ##class(Ens.Queue).Enumerate("BP.CartRecovery", .count, .list)
Write "Mensajes en cola: ", count, !Ejecutar DiagnosticReport (pre-compilado):
ZN "ECOMMERCE"
Do ##class(ECOM.Setup.DiagnosticReport).Run()Salida esperada:
=== ECOM Diagnostic Report ===
Generated: 2025-10-29 22:48:00
1. PRODUCTION STATUS
Running: 1
2. LOG SUMMARY
Total logs: 45
3. AUDIT SUMMARY
Total processed: 12
4. MESSAGE VIEWER
Go to: http://localhost:52773/csp/ecommerce/EnsPortal.MessageViewer.zen
Search by: CartId or SessionId
Crear rutinas de diagnóstico custom:
Archivo: src/ECOM/Setup/DebugTools.cls
Class ECOM.Setup.DebugTools
{
/// Buscar mensajes por CartId
ClassMethod FindByCartId(pCartId As %String)
{
Write "=== Messages for CartId: ", pCartId, " ===", !
Set sql = "SELECT h.ID, h.SessionId, h.TimeCreated, h.SourceConfigName, h.TargetConfigName, h.Status "_
"FROM Ens.MessageHeader h "_
"JOIN Ens_Util.Log l ON h.SessionId = l.SessionId "_
"WHERE l.Text LIKE ? "_
"ORDER BY h.TimeCreated DESC"
Set stmt = ##class(%SQL.Statement).%New()
Set sc = stmt.%Prepare(sql)
Set rs = stmt.%Execute("%"_pCartId_"%")
While rs.%Next() {
Write "Message ID: ", rs.ID, !
Write " Session: ", rs.SessionId, !
Write " Time: ", rs.TimeCreated, !
Write " Route: ", rs.SourceConfigName, " -> ", rs.TargetConfigName, !
Write " Status: ", rs.Status, !
Write "---", !
}
Quit $$$OK
}
/// Ver estado de la producción
ClassMethod ProductionStatus()
{
Write "=== Production Status ===", !
Set running = ##class(Ens.Director).IsProductionRunning()
Write "Running: ", running, !
If running {
Set prod = ##class(Ens.Director).GetActiveProductionName()
Write "Production: ", prod, !
// Listar componentes
Set sql = "SELECT Name, ClassName, Enabled FROM Ens_Config.Item WHERE Production = ?"
Set stmt = ##class(%SQL.Statement).%New()
Do stmt.%Prepare(sql)
Set rs = stmt.%Execute(prod)
Write !, "Components:", !
While rs.%Next() {
Write " ", rs.Name, " (", rs.ClassName, ") - Enabled: ", rs.Enabled, !
}
}
Quit $$$OK
}
/// Limpiar logs antiguos (útil para testing)
ClassMethod CleanOldLogs(pDaysOld As %Integer = 7)
{
Set cutoff = $HOROLOG - pDaysOld
Set sql = "DELETE FROM Ens_Util.Log WHERE TimeLogged < ?"
Set stmt = ##class(%SQL.Statement).%New()
Do stmt.%Prepare(sql)
Set result = stmt.%Execute(cutoff)
Write "Deleted ", result.%ROWCOUNT, " old log entries", !
Quit $$$OK
}
}Comandos útiles para debugging:
// Ver estado de producción
Do ##class(ECOM.Setup.DebugTools).ProductionStatus()
// Buscar todos los mensajes de un cart
Do ##class(ECOM.Setup.DebugTools).FindByCartId("test_017_COMPLETE")
// Ver globals de auditoría directamente
zwrite ^ECOM("Audit","Rows")
zwrite ^ECOM("Log","Rows")
// Reiniciar producción
Do ##class(Ens.Director).StopProduction()
Do ##class(Ens.Director).StartProduction("ECOM.Production")
// Ver configuración de un componente
Set item = ##class(Ens.Config.Item).%OpenId("ECOM.Production||BP.CartRecovery")
zwrite itemHealth Check:
curl http://localhost:52773/csp/ecommerce/healthEnviar carrito abandonado:
curl -X POST "http://localhost:52773/csp/ecommerce/vtex/abandoned" \
-H "Content-Type: application/json" \
-d '{
"cartId": "test_debug_001",
"customer": {"email": "debug@test.com", "name": "Debug User"},
"items": [
{"sku":"SKU-TEST","qty":1,"size":"M","color":"Red"}
]
}'Script de prueba múltiple:
#!/bin/bash
# test-multiple-carts.sh
for i in {1..10}; do
echo "Sending cart test_$i..."
curl -X POST "http://localhost:52773/csp/ecommerce/vtex/abandoned" \
-H "Content-Type: application/json" \
-d "{
\"cartId\": \"test_load_$i\",
\"customer\": {\"email\": \"load$i@test.com\", \"name\": \"Load Test $i\"},
\"items\": [{\"sku\":\"SKU-$i\",\"qty\":1,\"size\":\"M\",\"color\":\"Blue\"}]
}"
sleep 0.5
done
echo "Done. Check logs with: Do ##class(ECOM.Setup.DiagnosticReport).Run()"Acceso:
http://localhost:52773/csp/sys/UtilHome.csp
Rutas importantes:
- Message Viewer:
Interoperability > Monitor > Messages - Event Log:
Interoperability > Monitor > Event Log - Production Config:
Interoperability > Configure > Production - Queues:
Interoperability > Monitor > Queues - Business Rules:
Interoperability > Configure > Business Rules
Filtros útiles en Message Viewer:
- Por SessionId: Ver todo el flujo de un mensaje
- Por Time Range: Últimos 15 minutos
- Por Status: Error, Completed, Queued
- Por Source/Target: Filtrar por componente
Cuando un mensaje falla, seguir este orden:
-
✅ Verificar que la producción está corriendo
Write ##class(Ens.Director).IsProductionRunning()
-
✅ Revisar Event Log para errores
SELECT TOP 5 TimeLogged, ConfigName, Text FROM Ens_Util.Log WHERE Type = 2 ORDER BY ID DESC
-
✅ Buscar el mensaje en Message Viewer
- Por SessionId o CartId en el texto
-
✅ Ver el contenido del mensaje
Set header = ##class(Ens.MessageHeader).%OpenId(12345) zwrite header
-
✅ Verificar configuración del componente
- Management Portal > Production Config
- Revisar Settings (Enabled, PoolSize, etc.)
-
✅ Revisar globals de datos custom
zwrite ^ECOM
-
✅ Re-compilar si hay cambios
Do $SYSTEM.OBJ.Load("/external/src/ECOM/BP/CartRecovery.cls","ck")
-
✅ Restart Production si es necesario
Do ##class(Ens.Director).StopProduction() Do ##class(Ens.Director).StartProduction("ECOM.Production")
iris103/
├── README.MD # Este archivo
├── SPRINT_BACKLOG.md # Errores pendientes y mejoras
├── src/
│ └── ECOM/
│ ├── Production.cls # Definición de la producción
│ ├── BO/ # Business Operations
│ │ ├── CRMEmail.cls # Envío de emails
│ │ ├── SAPInventory.cls # Consulta SAP
│ │ └── Storage.cls # Auditoría y logs
│ ├── BP/ # Business Processes
│ │ └── CartRecovery.cls # BPL principal
│ ├── BS/ # Business Services
│ │ ├── VTEXPoller.cls # Polling (deshabilitado)
│ │ └── VTEXWebhook.cls # Webhook receiver
│ ├── Msg/ # Message classes
│ │ ├── CartAbandonedRequest.cls
│ │ ├── CartAbandonedResponse.cls
│ │ ├── SAPCheckRequest.cls
│ │ ├── SAPCheckResponse.cls
│ │ ├── CRMEmailRequest.cls
│ │ ├── CRMEmailResponse.cls
│ │ ├── CallRequest.cls
│ │ └── CallResponse.cls
│ ├── REST/ # REST API
│ │ └── VTEXWebhookAPI.cls # REST endpoints
│ └── Setup/ # Utilidades
│ ├── ConfigureWebApp.cls # Configuración de webapp
│ ├── ProductionManager.cls # Gestión de producción
│ ├── DiagnosticReport.cls # Reporte de diagnóstico
│ └── DebugTools.cls # Herramientas de debug (nuevo)