La Guía Definitiva: Estrategia, Desarrollo y Reportería
La hoja de ruta del proyecto (El "Macro").
Objetivo: Planificar el proyecto y preparar los recursos.
Es la fase de "Sizing". Se define la infraestructura (servidores, HANA vs. SQL, Cloud vs. On-premise) y el alcance inicial.
La Trampa: Un "Scope Creep" (alcance descontrolado). Si el alcance no se define y se firma aquí, el proyecto fracasará. "Definir lo que NO se va a hacer es tan importante como definir lo que SÍ se va a hacer."
Objetivo: Crear el "plano" de la solución. Esta es la fase MÁS CRÍTICA de toda la implementación.
La Trampa: Que el cliente no se involucre ("no tengo tiempo") o que el consultor no entienda el negocio. Un BBP débil o no firmado garantiza un desastre en la Fase 3, lleno de "esto no es lo que pedí".
Objetivo: Configurar y construir la solución definida en el BBP.
Esta es la fase de "manos a la obra" (construcción) donde los consultores y desarrolladores trabajan.
Objetivo: Validar el sistema, entrenar a los usuarios y prepararse para el "Go-Live".
La Trampa: Datos "basura". Si los datos migrados (Maestro de Items, Clientes) son de mala calidad, el sistema nuevo será inútil desde el primer día (GIGO: Garbage In, Garbage Out).
Objetivo: Iniciar la operación en el sistema nuevo y estabilizarlo.
La Trampa: Desaparecer después del Go-Live. El soporte "Hypercare" es vital para la adopción del usuario. Los problemas *siempre* surgen la primera semana, y la frustración del usuario debe ser manejada inmediatamente.
El árbol de decisión. (Clic para ver el detalle).
Qué es: Usar las herramientas estándar de SAP B1. Autorizaciones, numeración de documentos, propiedades, parametrizaciones de módulo, determinaciones de cuenta, etc.
Cuándo usarlo: SIEMPRE. Es el primer lugar donde debes buscar. Si el cliente dice "No quiero que el almacenero vea los costos", la solución es un permiso, no un Add-on.
Por qué: Es 100% estándar, 100% mantenible, 0% costo de desarrollo y está 100% garantizado que funcionará tras una actualización.
La Trampa: El consultor no conoce bien el sistema e informa que "no se puede" y lo pasa a desarrollo. El 90% de los requerimientos de un cliente *deberían* resolverse en este nivel.
Qué es: Consultas (Query Manager) y Búsquedas Formateadas (FMS). Lógica simple de SQL/HANA que se ejecuta en la interfaz de usuario.
Cuándo usarlo: Para alertas simples (Ej: "Alertar al crear un pedido si el cliente tiene saldo vencido") o para autocompletar campos (Ej: "Al seleccionar un item, traer el peso de OITM a un UDF en la línea de la factura").
Por qué: Es rápido de implementar (un query guardado) y no requiere desarrollo de software (.NET).
La Trampa: Un FMS *no es una validación de negocio robusta*. Se ejecuta en el cliente (UI) y puede ser lento si el query es pesado. Más importante: **es 100% omitido (bypassed) por una inserción vía DI-API o Service Layer**. Para validaciones de negocio *reales* (que no se pueden saltar), usa el Nivel 3.5: `TransactionNotification`.
Qué es: Campos Definidos por Usuario (UDF) y Tablas Definidas por Usuario (UDT). Es la forma estándar de extender el modelo de datos.
Cuándo usarlo: "Necesito guardar la 'Placa del Vehículo' en la Guía de Remisión" -> `UDF` en la tabla `ODLN`. "Necesito una tabla nueva para gestionar 'Tarifas por Distrito'" -> `UDT`.
Por qué: Es 100% estándar y mantenible. Las herramientas (DI-API, Service Layer, Crystal) las reconocen automáticamente. Es la base de casi cualquier personalización.
La Trampa: Crear 50 UDFs en la cabecera de la factura (OINV) cuando lo que realmente necesitabas era una UDT (tabla de cabecera) y un UDF (tabla de detalle) para guardar esa información. Abusar de los UDFs en tablas maestras las hace lentas.
Qué es: Usar el Query Manager o Crystal Reports para *presentar* información.
Cuándo usarlo: Cuando el usuario dice "Necesito un módulo para..." y, tras preguntar, te das cuenta que solo quiere *ver* datos. (Ej: "Necesito un módulo de Pedidos Pendientes").
Por qué: Un reporte es **10 veces más barato y rápido** de construir que un Add-on. Siempre pregunta: "¿Necesitas modificar esta información o solo verla?". Si es "solo verla", es un reporte.
La Trampa: Intentar construir reportes transaccionales complejos (como un P&L o un Balance) usando solo el Query Manager. Esos reportes requieren lógica de SPs y el poder de formato de Crystal Reports. (Ver Biblioteca de Reportería).
Qué es: El middleware de integración oficial de SAP. Es una plataforma gráfica (basada en XML/XSLT) para construir flujos de integración.
Cuándo usarlo: Para escenarios de integración complejos y robustos. Su uso estrella es **Intercompany** (sincronizar datos entre dos bases de datos SAP B1). También es la vía preferida para conectar B1 con R/3 o S/4HANA.
Por qué: Es muy potente, soporta colas de mensajes (asincronía) y tiene monitoreo de transacciones.
La Trampa: Es complicado de aprender y mantener. Es *overkill* (excesivo) para una integración simple como "conectar mi página web". Para eso, usa Service Layer.
Qué es: La API web moderna de SAP B1. Es RESTful, habla JSON y usa OData. Es el reemplazo de la funcionalidad *backend* de la DI-API.
Cuándo usarlo: ¡Para casi cualquier integración moderna! Conectar un E-commerce (Shopify, Magento, VTEX), un portal de clientes en PHP, una App móvil en React Native, un script de Python que corre en un servidor. Es independiente de la plataforma.
Por qué: Es rápido, stateless (no consume licencias por conexión persistente) y usa estándares web (HTTP, JSON).
La Trampa: Pensar que sirve para modificar la interfaz de SAP B1. No lo hace. El Service Layer es 100% *backend*. No puede usarse para añadir un botón a una ventana de SAP.
Qué es: El kit de desarrollo clásico (.NET C#/VB) que usa la **UI-API** (para modificar la interfaz) y la **DI-API** (para transacciones de backend).
Cuándo usarlo: ¡ÚLTIMO RECURSO! Cuando el requerimiento es SÍ O SÍ "Necesito un botón nuevo en la pantalla de Facturas", "Necesito una pantalla completamente nueva *dentro* de SAP B1" o "Necesito capturar un clic y bloquearlo con una lógica muy compleja".
Por qué: Es la *única* forma de alterar el comportamiento del cliente pesado (el SAP Business One.exe).
La Trampa: Usarlo para todo. Un Add-on es la solución más **cara**, más **lenta de desarrollar** y más **frágil** (requiere reinstalación y recompilación en actualizaciones mayores). Cada requerimiento que se resuelve con un Add-on en lugar de un nivel inferior es una deuda técnica que la empresa pagará por años.
Los "fierros" para ejecutar la estrategia.
Qué es: Una aplicación de escritorio (cliente) que permite la carga masiva de datos en SAP B1 usando plantillas de Excel (guardadas como .csv o .txt).
Cuándo se usa: Principalmente en la **Fase 4 (Preparación Final)** para la migración de datos.
Para qué se usa:
Mejor Práctica: Siempre usa el modo "Simulación" (`Run Simulation`) primero. El DTW usa la DI-API por debajo, por lo que simular la carga te dirá *todos* los errores de lógica de negocio (ej: "El artículo X no existe") sin insertar un solo registro.
Qué es: Un *único* Procedimiento Almacenado (Stored Procedure) en la base de datos (SBO_SP_TransactionNotification) que SAP B1 ejecuta *automáticamente* con cada transacción (Crear/Actualizar) de un objeto de negocio.
Cuándo se usa: En la **Fase 3 (Realización)** para implementar validaciones de negocio "inquebrantables".
Para qué se usa: Es la *única* forma 100% segura de bloquear una acción. A diferencia de un FMS (que se salta por la DI-API), el TN se ejecuta siempre.
IF @object_type = '13' AND @transaction_type IN ('A', 'U') ... (Bloquear una Factura - 13 - al Agregar 'A' o Actualizar 'U').La Trampa: Un TN mal escrito (un query lento o un bucle) puede **destruir el rendimiento** de *toda* la compañía, ya que se ejecuta en cada transacción. Debe ser híper-optimizado.
Qué es: El panel dentro de SAP B1 (Gestión -> Add-ons -> Gestor de Add-ons) que se usa para instalar y administrar los Add-ons del SDK.
Cuándo se usa: En la **Fase 3 y 4** para desplegar los desarrollos (Gaps) en los ambientes de QAS (para UAT) y Producción (para Go-Live).
Cómo funciona:
.ard (un manifest) y un .zip.La Trampa: Problemas de permisos de Windows (UAC) o de arquitectura (Add-on de 32-bits en cliente de 64-bits). Siempre probar la instalación en una máquina de usuario "limpia", no solo en la del desarrollador.
El "Cómo": DI-API (C#), Service Layer (API) y UI-API (Add-on)
SAPbobsCOM.Company oCompany = new SAPbobsCOM.Company();
oCompany.Server = "SAP-SERVER";
oCompany.CompanyDB = "SBODEMO_PERU";
oCompany.UserName = "manager";
oCompany.Password = "1234";
oCompany.DbServerType = SAPbobsCOM.BoDbServerTypes.dst_MSSQL2019;
int lRetCode = oCompany.Connect();
if (lRetCode != 0) {
Console.WriteLine("Error: " + oCompany.GetLastErrorDescription());
}
// Después de usar un objeto COM (ej. oInvoice)
if (oInvoice != null) {
System.Runtime.InteropServices.Marshal.ReleaseComObject(oInvoice);
oInvoice = null;
}
GC.Collect(); // Forzar recolección de basura
int lRetCode = oDocument.Add();
if (lRetCode != 0) {
string errorMsg;
oCompany.GetLastError(out lRetCode, out errorMsg);
throw new Exception($"Error SAP: {lRetCode} - {errorMsg}");
}
try {
oCompany.StartTransaction();
// ... (código que crea factura, ej. oInvoice.Add()) ...
if (oCompany.InTransaction) {
oCompany.EndTransaction(SAPbobsCOM.BoWfTransOpt.wf_Commit);
}
} catch (Exception) {
if (oCompany.InTransaction) {
oCompany.EndTransaction(SAPbobsCOM.BoWfTransOpt.wf_RollBack);
}
}
SAPbobsCOM.BusinessPartners oBP;
oBP = (SAPbobsCOM.BusinessPartners)oCompany.GetBusinessObject(SAPbobsCOM.BoObjectTypes.oBusinessPartners);
oBP.CardCode = "C-NUEVO-01";
oBP.CardName = "Cliente de Prueba";
oBP.CardType = SAPbobsCOM.BoCardTypes.cCustomer;
oBP.GroupCode = 100; // Asumiendo 100 es el grupo "Clientes"
oBP.Add();
// (No olvidar manejo de errores y liberar COM)
SAPbobsCOM.BusinessPartners oBP;
oBP = (SAPbobsCOM.BusinessPartners)oCompany.GetBusinessObject(SAPbobsCOM.BoObjectTypes.oBusinessPartners);
if (oBP.GetByKey("C-NUEVO-01")) { // ¡Importante buscarlo primero!
oBP.Phone1 = "999-888-777";
oBP.EmailAddress = "test@cliente.com";
oBP.Update();
}
SAPbobsCOM.Documents oInvoice;
oInvoice = (SAPbobsCOM.Documents)oCompany.GetBusinessObject(SAPbobsCOM.BoObjectTypes.oInvoices);
oInvoice.CardCode = "C-NUEVO-01";
oInvoice.DocDate = DateTime.Now;
// Línea 1
oInvoice.Lines.ItemCode = "ITEM001";
oInvoice.Lines.Quantity = 10;
oInvoice.Lines.Price = 150.0;
oInvoice.Lines.Add();
// Línea 2
oInvoice.Lines.ItemCode = "ITEM002";
oInvoice.Lines.Quantity = 5;
oInvoice.Lines.Price = 300.0;
oInvoice.Lines.Add();
oInvoice.Add();
// (Usar Transacciones, Manejo de Errores y Liberar COM)
SAPbobsCOM.Documents oOrder;
oOrder = (SAPbobsCOM.Documents)oCompany.GetBusinessObject(SAPbobsCOM.BoObjectTypes.oOrders);
if (oOrder.GetByKey(1234)) { // 1234 = DocEntry del Pedido
oOrder.Close();
}
SAPbobsCOM.Recordset oRecordset;
oRecordset = (SAPbobsCOM.Recordset)oCompany.GetBusinessObject(SAPbobsCOM.BoObjectTypes.BoRecordset);
string sql = "SELECT CardName, Balance FROM OCRD WHERE CardCode = 'C-NUEVO-01'";
oRecordset.DoQuery(sql);
if (!oRecordset.EoF) { // EoF = End of File
string name = oRecordset.Fields.Item("CardName").Value.ToString();
double balance = (double)oRecordset.Fields.Item("Balance").Value;
}
// (Liberar COM para oRecordset)
SAPbobsCOM.UserFieldsMD oUserField;
oUserField = (SAPbobsCOM.UserFieldsMD)oCompany.GetBusinessObject(SAPbobsCOM.BoObjectTypes.oUserFields);
oUserField.TableName = "OITM"; // Tabla de Artículos
oUserField.Name = "MiCampoExtra";
oUserField.Description = "Mi Campo Extra";
oUserField.Type = SAPbobsCOM.BoFieldTypes.db_Alpha;
oUserField.EditSize = 50;
oUserField.Add();
// POST https://sap-server:50000/b1s/v1/Login
{
"CompanyDB": "SBODEMO_PERU",
"UserName": "manager",
"Password": "1234"
}
// Respuesta: Devuelve una Cookie (B1SESSION)
// POST .../b1s/v1/Orders
{
"CardCode": "C-NUEVO-01",
"DocDueDate": "2025-11-10",
"Comments": "Pedido desde E-commerce",
"DocumentLines": [
{ "ItemCode": "ITEM001", "Quantity": 10 },
{ "ItemCode": "ITEM002", "Quantity": 5 }
]
}
// POST .../b1s/v1/Invoices
{
"CardCode": "C001", "DocDueDate": "2025-11-10",
"DocumentLines": [
{
"BaseType": 17, // 17 = Pedido (ORDR)
"BaseEntry": 1234, // DocEntry del Pedido
"BaseLine": 0 // Número de línea del Pedido
},
{
"BaseType": 17,
"BaseEntry": 1234,
"BaseLine": 1
}
]
}
// PATCH .../b1s/v1/Orders(1234)
{ "Comments": "Nuevo comentario del cliente." }
// PATCH .../b1s/v1/Orders(1234)
{
"DocumentLines": [
{
"LineNum": 1, // Línea que queremos cerrar
"LineStatus": "bost_Close"
}
]
}
// GET .../b1s/v1/BusinessPartners('C-NUEVO-01')?$select=CardName,Balance,EmailAddress
// GET .../b1s/v1/Items?$filter=ItemsGroupCode eq 102 and Price gt 100
// GET .../b1s/v1/Invoices?$top=20&$skip=40
// Trae 20 facturas, saltándose las primeras 40 (Página 3)
// GET .../b1s/v1/Orders(1234)?$expand=DocumentLines
// GET .../b1s/v1/sml.svc/MY_QUERY_NAME
// (Asumiendo que creaste un "Query" o "Vista" en B1)
// En el Main() de tu Add-on .exe
SAPbouiCOM.SboGuiApi oGuiApi = new SAPbouiCOM.SboGuiApi();
string sConnectionString = Environment.GetCommandLineArgs().GetValue(1).ToString();
oGuiApi.Connect(sConnectionString);
SAPbouiCOM.Application SBO_Application = oGuiApi.GetApplication();
// Conectarse a la DI-API (para transacciones)
oCompany = (SAPbobsCOM.Company)SBO_Application.Company.GetDICompany();
// Suscribirse al evento
SBO_Application.ItemEvent += SBO_Application_ItemEvent;
//...
private void SBO_Application_ItemEvent(string FormUID, ref SAPbouiCOM.ItemEvent pVal, out bool BubbleEvent)
{
BubbleEvent = true;
// Si es un clic, en el form de Factura (133), en el botón "Crear" (1)
if (pVal.EventType == SAPbouiCOM.BoEventTypes.et_ITEM_PRESSED &&
pVal.FormType == 133 && pVal.ItemUID == "1" && pVal.BeforeAction)
{
// ... poner aquí la lógica de validación ...
// SBO_Application.MessageBox("¡Validación falló!");
// BubbleEvent = false; // Esto cancela el clic
}
}
// Obtener el formulario activo
SAPbouiCOM.Form oForm = SBO_Application.Forms.ActiveForm;
// Obtener el campo (EditText) por su UID
SAPbouiCOM.EditText oCardCode = (SAPbouiCOM.EditText)oForm.Items.Item("4").Specific;
SAPbouiCOM.EditText oComments = (SAPbouiCOM.EditText)oForm.Items.Item("16").Specific;
// Leer valor
string cliente = oCardCode.Value;
// Escribir valor
oComments.Value = "Valor puesto por mi Add-on";
// (Capturar el evento de Form_Load para el form de BP: 134)
SAPbouiCOM.Form oForm = SBO_Application.Forms.GetForm("134", pVal.FormUID);
SAPbouiCOM.Item oNewItem;
oNewItem = oForm.Items.Add("MiBoton", SAPbouiCOM.BoFormItemTypes.it_BUTTON);
oNewItem.Left = oForm.Items.Item("2").Left + oForm.Items.Item("2").Width + 10;
oNewItem.Top = oForm.Items.Item("2").Top;
oNewItem.Width = 100;
SAPbouiCOM.Button oButton = (SAPbouiCOM.Button)oNewItem.Specific;
oButton.Caption = "Acción Custom";
El "Cómo": Consultas SQL vs. HANA y el Proceso de Crystal Reports
Top 5 Clientes:
SELECT TOP 5 T0.CardName, SUM(T0.DocTotal)
FROM OINV T0 WITH (NOLOCK)
GROUP BY T0.CardName ORDER BY SUM(T0.DocTotal) DESC
Manejo de Nulos:
SELECT T0.ItemCode, ISNULL(T0.U_MiCampo, 'Valor Vacio')
FROM OITM T0 WITH (NOLOCK)
Fecha Actual:
SELECT T0.DocNum FROM ORDR T0
WHERE T0.DocDueDate < GETDATE()
Subqueries:
SELECT T0.ItemCode,
(SELECT SUM(T1.OnHand) FROM OITW T1 WHERE T1.ItemCode = T0.ItemCode) AS 'TotalStock'
FROM OITM T0
Pedidos Abiertos:
SELECT T0.DocNum, T1.ItemCode, T1.OpenQty
FROM ORDR T0 JOIN RDR1 T1 ON T0.DocEntry = T1.DocEntry
WHERE T1.LineStatus = 'O'
Top 5 Clientes:
SELECT T0."CardName", SUM(T0."DocTotal")
FROM "OINV" T0
GROUP BY T0."CardName" ORDER BY SUM(T0."DocTotal") DESC
LIMIT 5
Manejo de Nulos:
SELECT T0."ItemCode", IFNULL(T0."U_MiCampo", 'Valor Vacio')
FROM "OITM" T0
Fecha Actual:
SELECT T0."DocNum" FROM "ORDR" T0
WHERE T0."DocDueDate" < NOW()
Subqueries (HANA prefiere JOINs):
SELECT T0."ItemCode", SUM(T1."OnHand") AS "TotalStock"
FROM "OITM" T0
LEFT JOIN "OITW" T1 ON T0."ItemCode" = T1."ItemCode"
GROUP BY T0."ItemCode"
Pedidos Abiertos:
SELECT T0."DocNum", T1."ItemCode", T1."OpenQty"
FROM "ORDR" T0 JOIN "RDR1" T1 ON T0."DocEntry" = T1."DocEntry"
WHERE T1."LineStatus" = 'O'
Crystal se usa cuando un Query no es suficiente (formatos, logos, gráficos, códigos de barra, lógica compleja).
Tienes dos opciones en el `Database Expert`:
Opción A (Simple): Añade tablas (Ej: `OINV`, `INV1`, `OCRD`) y únelas visualmente en la pestaña `Links`. Bueno para reportes simples.
Opción B (Mejor Práctica):
Para que SAP B1 "hable" con Crystal, tus parámetros en Crystal (`Parameter Fields`) deben tener nombres especiales:
Aquí está el poder de Crystal: