CommunityProgramación y desarrollogithub.com

yanghangmvp/sap-abap-pitfalls

Base de conocimientos de dificultades y soluciones en desarrollo ABAP (reutilizable entre proyectos). Recopila más de 120 entradas de «fenómeno → causa raíz → solución» a partir de más de 100 informes de entrega y registros de corrección diaria de proyectos ERP de clientes de muestra, además de una checklist de inicio de proyecto (cinco fases: evaluación, pre-implementación, autocomprobación de codificación, entrega y cierre).

¿Qué es sap-abap-pitfalls?

Este skill está diseñado para desarrolladores ABAP que enfrentan problemas recurrentes y difíciles de diagnosticar en proyectos SAP. Proporciona un repositorio estructurado de más de 120 casos de error, cada uno documentado con el fenómeno observable, la causa raíz y la solución probada. El skill es especialmente valioso porque se enfoca en «fallos silenciosos» (donde SAP reporta éxito pero los datos no se modifican o son incorrectos) y en errores de sintaxis engañosos donde el mensaje de error del compilador ABAP apunta a una ubicación diferente a la del error real. Es útil en cualquier etapa de un proyecto ABAP: al inicio, se recomienda leer la checklist de arranque (§11) para evitar errores comunes; durante el desarrollo o la depuración, se puede consultar el índice de síntomas (§10) para localizar rápidamente la sección relevante. El skill cubre una amplia gama de temas, incluyendo activación y escritura de objetos (ADT/MCP), restricciones sintácticas de ABAP, fallos silenciosos en BAPIs y funciones estándar, manejo de transacciones y LUW, cuadros de diálogo y F4, ALV, JSON e interfaces de gateway, creación de objetos DDIC, técnicas de diagnóstico con ST22, y trampas en scripts de parches. Las tres reglas meta del skill son: 1) El texto del error a menudo no está relacionado con la causa real del problema; 2) Lo más peligroso no es un error, sino un fallo silencioso; 3) Ante cualquier fallo, lo primero es consultar ST22, no adivinar. Está pensado para herramientas de IA como Claude Code, Cursor o Codex, y su contenido se basa en la experiencia real de múltiples proyectos de entrega de SAP.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/yanghangmvp/MCP_ABAP/tree/HEAD/skills/sap-abap-pitfalls

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Base de conocimientos de dificultades y soluciones en desarrollo ABAP

Uso: Leer antes de comenzar un nuevo proyecto; para solucionar problemas, buscar por síntoma. Este skill solo recoge experiencias «reutilizables entre proyectos». Las direcciones de entorno, números de solicitud y listas de objetos se encuentran en el resumen general del proyecto (por ejemplo, sap-abap-project-ops). Las 120 entradas completas (con fragmentos de código y referencias) están en references/casebook.md.

Cómo usar: Nuevo proyecto → ir directamente a §11 Checklist de inicio (ejecutar en orden cronológico); proyecto ya iniciado con problemas → §10 Búsqueda rápida por síntoma para localizar la sección; para profundizar en alguna entrada con código completo y referencias → references/casebook.md.

Tres meta-reglas (más importantes que cualquier error individual):

  1. El texto del error a menudo no está relacionado con la causa real — El punto donde el compilador ABAP reporta el error es donde «encuentra la inconsistencia», no donde «escribiste mal». Asume primero que es un problema de orden de sentencias/orden de palabras clave/tipo no coincidente, no de sintaxis en sí.
  2. Lo más peligroso en SAP no son los errores, sino los fallos silenciosos — Los datos se corrompen silenciosamente bajo la triple ilusión de «retorno S, activación 200, compilación exitosa». Siempre que se trate con BAPI, SELECT INTO TABLE, EXTENSIONIN, elementos de texto o campos de mejora, haz una conciliación con la base de datos, no confíes en el valor de retorno.
  3. Ante un fallo, consulta ST22, no adivines — El Error analysis de un volcado corto te da directamente procedure "XXX" "(FORM)" y la clase de excepción CX_SY_*, lo que es un orden de magnitud más rápido que probar FORM por FORM.

0. Diez reglas de oro (infringirlas implica rehacer)

  1. Al modificar cualquier objeto existente, el primer paso es leer la versión activa de producción. Los usuarios a menudo modifican objetos de producción directamente en SE80 sin devolverlos al archivo; el archivo local solo sirve para comparación, no como línea base. get_source → diff con copia de seguridad para confirmar 0 diferencias → aplicar parche → leer de nuevo y diff → sobrescribir archivo en sentido inverso.
  2. Los elementos de texto también son parte de la línea base; get_source no incluye TEXTPOOL. Antes de sobrescribir el texto, debes read_textelements para obtener el texto completo de la versión activa, hacer diff con el JSON local, fusionar manualmente y luego subirlo. De lo contrario, las modificaciones de texto que el usuario haga en el frontend de SE38 se sobrescribirán sin dejar rastro, y el diff del código fuente no mostrará ninguna diferencia.
  3. Nunca uses UPDATE / MODIFY directamente en tablas estándar. Para modificar documentos FI ya contabilizados, usa FI_DOCUMENT_CHANGE (grupo FACG, T_ACCHG solo llena FDNAME+NEWVAL, el encabezado no pasa I_BUZEI, solo las partidas individuales).
  4. No te inventes números de solicitud. Si falta CORR_NR al subir un objeto transportable ⇒ detente e informa, espera a que el usuario dé el número; antes de crear un nuevo objeto TADIR, primero informa de «tipo + nombre sugerido + resultado de comprobación de duplicados en TADIR + motivo para continuar la numeración», espera confirmación para crearlo. Las modificaciones posteriores a objetos existentes que ya tienen una solicitud asignada reutilizan el número original, no preguntes de nuevo.
  5. No uses clases Update* para hacer llamadas de prueba a interfaces. Realmente escriben — se usó UpdateFunctionModule para pasar solo el cuerpo de la función «para probar», sobrescribiendo el código fuente inactivo con una versión truncada de 620 caracteres. Las sondas solo usan interfaces de solo lectura.
  6. Cuatro pasos de entrega: Generar → Subir código fuente → Subir elementos de texto (PROG/PX deben activarse por separado) → Generar estado de GUI (CUA). Si los elementos de texto no están activos, en tiempo de ejecución TEXT-xxx estará siempre vacío.
  7. Comentarios en código fuente de producción: deja el esqueleto, elimina la narrativa. Conserva el encabezano del programa (unidad/autor/fecha/resumen/registro de cambios) + líneas de marco *&--- + pancartas de FORM + documentación de parámetros -->/<-- + comentarios cortos de criterios; elimina la justificación del diseño, descripción de criterios, resultados de pruebas conjuntas, razones de TODO, referencias a scripts/work/ADT/ZTEST_*. Después de la separación, debes hacer diff para demostrar que el código en sí no ha cambiado.
  8. Los objetos de prueba/sonda deben eliminarse inmediatamente después de la prueba. Primero get_source para archivar en backup/ → eliminar → verificar que TADIR se ha limpiado. Solo elimina los que creaste tú en esta ronda; los creados por otros, los que ya tienen paquete/solicitud asignados y las DEMO en uso no se tocan.
  9. Primero verifica bloqueos, luego haz copia de seguridad, antes de escribir en SAP. Los archivos de credenciales no se transmiten a terceros, no se muestran ni se escriben en los entregables.
  10. Cuando el usuario dice «los datos están bien», más razón para asumir que el programa tiene un problema. Invierte el «importe del error + número de documento» para deducir las características de entrada, escribe una sonda que reproduzca el error carácter por carácter; si la reproducción es exitosa, la causa raíz está identificada.

1. Canales de activación y escritura (ADT / MCP)

  • HTTP 200 ≠ activación exitosa — El éxito o fracaso está en el body: debes comprobar explícitamente activationExecuted="false" y lanzar un error; la única forma fiable de verificar la activación es leer el include o comparar las versiones ?version=active e inactive. TRDIR-RSTAT no escribe valores en S/4.
  • El falso éxito es el más difícil de detectar — Los scripts antiguos capturaban mensajes con expresiones regulares como <chklst:shortText> / <msg>text</msg>, pero la estructura real es prefijo chkl: + <shortText><txt> + msg con atributos ⇒ no se capturaba ni un mensaje, reportando «activación exitosa» tres veces seguidas cuando en realidad era una cáscara vacía de 131 caracteres. Si no se capturan mensajes, debe tratarse como fallo.
  • Los elementos de texto son objetos activables independientes PROG/PX — Activar el programa padre no los activa automáticamente; si push_textelements solo tiene GET→LOCK→PUT→UNLOCK sin ACTIVATE, PUT solo escribe la versión inactiva. Al pasar PROG/P, SAP considera que no hay contenido activable y devuelve activationExecuted="false" sin ningún mensaje ⇒ inactividad silenciosa. La interfaz de activación no acepta corrNr (si se envía da 400).
  • El PUT de push_textelements sobrescribe todo el bloque, el orden de envío no puede invertirse — Nuevos símbolos: primero elementos de texto luego código fuente; eliminación de símbolos: primero código fuente luego elementos de texto. TE_FILE debe incluir todas las selecciones existentes completas.
  • Cuando los elementos de texto solo tienen versión inactiva, el programa debe regenerarse para que surtan efecto — Simplemente subir los elementos de texto + volver a PUT el mismo código fuente no los refrescará. Modifica una línea del código fuente (aunque sea un comentario) y luego envíalo de nuevo, o reconstruye el programa.
  • Tres tipos de «falso bloqueo»: ① El mensaje «bloqueado en la solicitud yyy del usuario xxx» en realidad es falta de envío de transport_request/corrNr; ② UpdateDataElement reporta locked by another user pero en realidad es un 400 «ya existe» — el isAlreadyExistsError() del handler solo reconoce inglés, el mensaje en chino no se detecta, y al caer en el caso por defecto 403 se traduce como «bloqueado» (⇒ en entornos ZH, MCP no puede modificar elementos de datos existentes; UpdateDomain no tiene esta limitación); ③ 403 … not specified for this client es sociedad incorrecta.
  • El único bloqueo real que se puede eliminar es a través de SM12 — Los tres tipos de bloqueo ESDICT/ESRDIRE/SEOCLSENQ están vinculados a sesiones ADT. DEQUEUE_ALL, _scope='3' y DEQUEUE_EESDICT son todos ineficaces. Después de confirmar con ENQUEUE_READ, pide al usuario que los elimine manualmente en SM12 y vuelve a ejecutar inmediatamente; no esperes a que expire la sesión.
  • Dos mensajes 403 tienen significados completamente diferentes — User XXX is currently editing ZFIDxxx = un usuario real tiene abierto SE80, espera; Object ... locked in task DEVK90011x not specified for this client = sociedad incorrecta, cambia a la sociedad de la solicitud y reintenta. Confundirlos te hará dar un rodeo innecesario.
  • El encabezado Accept del LOCK de ADT debe incluir el datatype — Si solo envías application/xml, SAP ya ha bloqueado con éxito pero responde 406 sin LOCK_HANDLE ⇒ fuga de bloqueo; todas las solicitudes posteriores reportan «el usuario X está editando actualmente», engañándote para que creas que otro lo tiene ocupado. Valor correcto: application/vnd.sap.as+xml;dataname=com.sap.ad.lock.result.
  • process.exit() en el fail() del script salta finally ⇒ el bloqueo de cola permanece — Tras una salida anormal de un script de tipo push, el bloqueo SEOCLSENQ puede persistir hasta 40 minutos, haciendo que todos los LOCK posteriores reporten 403 como si «el usuario tuviera SE80 abierto». Al escribir scripts, primero haz UNLOCK y luego sal, o cambia a process.exitCode = 1.
  • No elimines por el usuario los bloqueos de edición por la fuerza — Eliminar bloqueos ADT podría perder las modificaciones no guardadas de la otra parte.
  • Los includes son recursos ADT independientes — Un POST a /programs/programs siempre crea un programa ejecutable (TRDIR-SUBC=1), que no se puede cambiar después; los includes se envían a /sap/bc/adt/programs/includes (elemento raíz <include:abapInclude>). El programa principal y el include deben estar en la misma solicitud de activación, de lo contrario se reportarán mutuamente como no definidos.
  • El include creado por INSERT REPORT ... STATE 'A' escribe el código fuente en versión activa — Si luego modificas la versión inactiva, la activación del programa principal aún compilará con la versión activa antigua y los errores apuntarán al código antiguo. Después de la modificación, debes ejecutar INSERT REPORT 'X' FROM lt_src STATE 'A' PROGRAM TYPE 'I'. (STATE debe estar antes de PROGRAM TYPE) para refrescar la versión activa.
  • El proxy local puede colgar las solicitudes ADT — Se manifiesta exactamente igual que «no se puede conectar a SAP» (socket hang up / error CSRF). Antes de generar el proceso hijo del MCP, elimina *_proxy y establece NO_PROXY=*; todas las solicitudes de escritura deben usar proxy: false.
  • ADT no tiene recursos de dynpro / GUI Status / Menu — Pero CUA se puede manejar mediante código (RS_CUA_INTERNAL_FETCH + _WRITE, state='A', escribir de vuelta es efectivo). D020S es el encabezado de la pantalla dynpro, no es la tabla de estados de GUI; RS_CUA_GET_STATUS no se puede usar para determinar, solo se reconoce RS_CUA_INTERNAL_FETCH (los 10 TABLES obligatorios deben darse completos, ORDER BY pposition para evitar el truncamiento de 25 líneas).

2. Restricciones sintácticas duras (compilar a la primera)

  • Cuatro tabúes para parámetros FORM: ① No se puede escribir TYPE c LENGTH n (reporta Numeric literals cannot be used as field names) ⇒ usa TYPE char<n> o tipo DDIC; ② Un parámetro de longitud fija que recibe una longitud diferente causa un error de incompatibilidad en tiempo de compilación, excepto para símbolos de texto, pero en tiempo de ejecución sigue siendo 500 Type conflict when calling a FORM ⇒ para longitudes variables usa el genérico TYPE c; ③ No se puede usar inline TYPE STANDARD TABLE OF (el parámetro se calcula como 8, reporta Different number of parameters) ⇒ primero nombra con TYPES; ④ FIELD-SYMBOL declarado en un FORM es local, para usarlo en otro FORM debe ser global.
  • FORM ... TABLES debe escribirse antes de USING/CHANGING — Si se escribe USING ... CHANGING ... TABLES reporta Field "TABLES" is unknown. (trata la palabra clave como nombre de parámetro). Lo mismo aplica en el lado de la llamada PERFORM.
  • El orden de las secciones en CALL FUNCTION es obligatorio — EXPORTING → IMPORTING → CHANGING → TABLES → EXCEPTIONS. Poner TABLES antes de IMPORTING reporta Unable to interpret "EV_OBJNR", sin que parezca un problema de orden. Los parámetros EXPORTING del lado llamado deben ir en la sección IMPORTING del lado llamante; si se invierte, reporta CALL_FUNCTION_PARM_UNKNOWN — muy fácil de malinterpretar como «la interfaz del FM no se generó».
  • Las selecciones de múltiples campos en SELECT deben separarse con comas, y las variables huésped @ deben escaparse — Reporta elements in the "SELECT LIST" list must be separated using commas. Lo mismo aplica para múltiples asignaciones en UPDATE ... SET; las constantes en WHERE (incluyendo CONSTANTS) también necesitan @.
  • La posición de INTO y UP TO n ROWS depende de lo que reporte el activador — Las sentencias con JOIN/subconsultas/expresiones SQL están en modo estricto, forzando INTO al final (SELECT 563); UP TO n ROWS debe ir antes de WHERE. Ambas formas se comportan de manera diferente en diferentes sentencias, copiar de memoria puede fallar.
  • ASSIGN ... CASTING TYPE c LENGTH <variable> no está soportado — La longitud debe ser un literal; si se omite LENGTH, en caso de índice fuera de rango se produce DUMP Invalid subfield access. La solución correcta es usar cl_abap_container_utilities=>fill_container_c( ) (inversa de la función estándar READ_CONTAINER_C).
  • El TYPE de DESCRIBE FIELD debe escribirse antes de COMPONENTS — Incluso si solo quieres el número de componentes, debes escribirlo: DESCRIBE FIELD cs_data TYPE lv_type COMPONENTS lv_count.
  • CONSTANTS debe llevar VALUE — CONSTANTS: c_x TYPE flag. reporta "VALUE ..." is not specified.
  • MESSAGE TEXT-xxx TYPE 'S' WITH n no es válido — Los símbolos de texto no pueden usarse como texto de mensaje con WITH. Usa un FORM auxiliar: lv_text = pv_text → REPLACE '&' IN lv_text WITH lv_num → MESSAGE lv_text TYPE 'S'.
  • Los tipos de longitud fija no tienen el nombre char<n> — TYPE char1000/char55/char220 son todos ilegales, reportan Type "X" is unknown; usa siempre TYPE c LENGTH n. Adivinar el nombre del tipo = perder una ronda de activación. Si no estás seguro, consulta DD04L o get_source.
  • class=>method solo se puede usar para métodos estáticos — Para métodos de instancia debes usar CREATE OBJECT lo. lo->method( ), de lo contrario reporta can only be specified with static methods.
  • Las plantillas de cadenas no pueden ser argumentos de PERFORM ... USING — Reporta Field "|" is unknown; primero asigna a una variable y luego pasa.
  • CALL TRANSACTION ... WITH f1 v1 no es una sintaxis válida — WITH solo se usa con AUTHORITY-CHECK. Para pasar parámetros solo se puede usar USING bdcdata; la primera pantalla de SM30 solo tiene un campo con el nombre de la tabla (VIMDYNFLDS no tiene campo de modo de actualización), para entrar en modo de modificación usa VIEW_MAINTENANCE_CALL + action = 'U'.
  • REPLACE ALL OCCURRENCES OF ... WITH '' en este sistema de tipos puede causar un bucle infinito — Para eliminar espacios/caracteres, cambia a filtrado carácter por carácter con DO strlen TIMES + lv_txt+lv_i(1).
  • CONCATENATE no acepta tipos numéricos — lv_cnt TYPE i reporta must be character-like; las funciones integradas (strlen/lines) no pueden usarse directamente en && (reporta The syntax for a method specification is objref->method, un error engañoso sobre sintaxis de flecha). Los valores numéricos siempre deben usar plantillas de cadenas |...{ lv_cnt }...|.
  • Dentro de una sonda $TMP no se pueden definir FORM; los comentarios fuera del cuerpo de un método no se guardan — La activación de la sonda reporta Statement is not accessible (CheckProgram no reporta esto); los comentarios entre ENDMETHOD y el siguiente METHOD en una clase reportan unknown comments which can't be stored.
  • Los nombres de SELECT-OPTIONS deben tener ≤ 8 caracteres — 9 caracteres son rechazados en la primera activación. Al acortar el nombre, debes cambiar sincrónicamente el nombre de la entrada selections en los elementos de texto (las variantes antiguas almacenan el nombre antiguo y se ignoran silenciosamente, solo se muestra un aviso no un error).

3. Lista de fallos silenciosos (la sección más cara)

Si el resultado es «retorno S / activación 200 / compilación exitosa» pero el resultado es incorrecto, primero ven aquí a comparar.

3.1 Desplazamiento y pérdida de datos

  • SELECT con lista explícita de columnas INTO TABLE <tabla completa predeclarada> asigna por posición — Si los tipos son compatibles, no reporta error ni advertencia: DOMVAL de DD07T cae en DOMNAME, WKGBTR de COEP cae en MEGBTR (importe siempre vacío), ZTEPIC3A reporta component "ZJK" not compatible with "ZJYRQ" (se obtuvo la coordenada con _act_dbg). Un mismo error recurrió en 5 informes. Tres soluciones: SELECT * / INTO TABLE @DATA(...) inline (la estructura se define por la lista) / crear una estructura estrecha con el mismo orden de columnas; o INTO CORRESPONDING FIELDS OF TABLE.
  • Usar campos de la tabla derecha de un LEFT JOIN en WHERE ⇒ semánticamente se degrada a INNER JOIN — Cuando la tabla derecha no tiene coincidencias, los campos son NULL y la condición no se cumple. Mueve los filtros a ON.
  • DELETE ADJACENT DUPLICATES ... COMPARING <campo no clave> puede eliminar datos de más — La tabla condicional A017 tiene clave principal que incluye DATBI, KNUMH no es clave; al eliminar duplicados COMPARING knumh, se eliminan todos los períodos de validez posteriores con el mismo KNUMH. Primero haz SORT BY con la clave completa y luego COMPARING con la clave completa.
  • AT NEW / AT END OF agrupa por adyacencia física; después de la modificación ALV, falla — REUSE_ALV_* reordena t_outtab en el lugar (si el usuario ordena por una columna de mensaje, se dispara), los mismos grupos ya no son adyacentes y AT END OF se dispara varias veces. El agrupamiento debe cambiarse a «bucle externo sobre claves de agrupación × bucle interno recolectando por clave», los límites del grupo solo reconocen la clave de negocio.
  • APPEND modifica sy-tabix — Después de APPEND INITIAL LINE ... ASSIGNING <fs>, obtener sy-tabix da el número de segmento, no el número de línea original ⇒ el rango se amplía, 128 líneas se convierten en 558 (el mismo registro se repite 43 veces). Al inicio del bucle, primero guarda lv_tabix = sy-tabix..
  • READ TABLE ... WITH KEY que no encuentra clave pone sy-tabix a 0 — Luego APPEND sy-tabix TO lt_del lo agrega a la lista de eliminación, y DELETE ... INDEX 0 produce DUMP TABLE_INVALID_INDEX. Guarda el número de línea al inicio del bucle y antes de eliminar, comprueba IF idx >= 1 AND idx <= lines( ).
  • Una declaración inline DATA(...) dentro de una rama IF que no se ejecuta significa que la tabla interna nunca se crea ⇒ el LOOP posterior produce un volcado corto. Mueve la declaración a la cabecera del método, o antes de la obtención de datos, haz una comprobación de tabla vacía.

3.2 BAPI / funciones estándar «retornan S o no reportan error pero no escriben»

  • La estructura x de un BAPI debe enviarse emparejada con la estructura original — Si solo envías depreciationareas sin depreciationareasx, al rellenar AFASL reporta No change parameter given for depreciation area 01. La falta de la estructura x no produce error de sintaxis, solo se dispara cuando se cumplen las condiciones de datos.
  • El elemento de datos de la estructura de identificación de campos de mejora SD debe ser CHAR1 — SD_SALESDOCUMENT_CHANGE usa GET_CHANGEABLE_FIELDS, solo examina los campos con WHERE rollname = 'CHAR1' OR 'CHAR01'; los campos con BAPIUPDATE son todos excluidos ⇒ retorna S, el valor no cambia, cero errores, pero la ruta de creación no usa esta lista ⇒ típico «crear puede escribir, modificar no puede». No se puede cambiar de nuevo a BAPIUPDATE.
  • BAPIPAREX-VALUEPART1 tiene solo 240 caracteres — Las funciones estándar leen VALUEPART1~4 secuencialmente por desplazamiento (total 960), si solo se llena el primer segmento, el final se trunca silenciosamente (BAPE_VBAP probado con 271 caracteres, los últimos 3 campos quedan vacíos, sin error). Divide en cuatro segmentos de 240 caracteres.
  • Tipos de pedido de abono/cargo sin planificación: la cantidad solo reconoce TARGET_QTY — En BAPISDITM, ningún campo tiene como referencia KWMENG; esta categoría de posición no tiene configuración en TVEPZ ⇒ no se generan planificaciones, el BAPISCHDL enviado se descarta silenciosamente, retorna S pero KWMENG=0, NETWR=0, VBEP 0 líneas. El criterio de diagnóstico es si TVEPZ tiene entrada, no se puede usar TVAP-LFREL (tanto Z001 como Z014 tienen LFREL vacío), ni VBTYP. Misma trampa en lectura: VBAP-KWMENG siempre es 0, la cantidad está en VBAP-ZMENG ⇒ criterio de relleno «priorizar KWMENG no vacío, si está vacío usar ZMENG». El primer campo de BAPE_* es la clave; si se omite en modo modificación, se borra el número de documento — La función estándar hace move-corresponding del valor EXTENSIONIN a E_VBAKKOM, si VBELN se deja vacío, sobrescribe el número de documento a vacío ⇒ V1 045 seguido de V4 219 (la causa raíz está en el mensaje E anterior al V4 219).
  • Los documentos de referencia SD son un trío — REF_DOC + REF_DOC_IT + REF_DOC_CA (categoría de documento de referencia); si solo envías los dos primeros, reporta V2 068 sin categoría de documento pero contiene C (&2 vacío). Si el número de línea es 0 o el documento no existe, es el mismo mensaje pero &3 está vacío. La categoría se toma del propio documento de referencia VBAK-VBTYP, no del encabezado VGTYP.
  • SD_SALESDOCUMENT_CHANGE ordena las dos tablas de entrada por separado — Las líneas que se eliminan deben tener tanto ITEM_IN como ITEM_INX con ITM_NUMBER; si solo se llena ITEM_INX, se desalinea y reporta un error completamente no relacionado T184 no hay categoría de posición disponible.
  • BAPI_USER_EXISTENCE_CHECK en este sistema devuelve 0 para cualquier cuenta (incluso ZZZZZZ999 pasa) ⇒ la comprobación es simbólica, los documentos transferidos a cuentas inválidas no son visibles en ninguna de las dos interfaces. Consulta directamente USR02 + protección de longitud (0 < len <= 12).
  • DELETE_TEXT sobre un texto inexistente RAISING NOT_FOUND — La función estándar agrega este mensaje E a BAPIRET2, y frm_check_return lo trata como fallo ⇒ «vaciar» se convierte en error E. Antes de vaciar, consulta STXH (TDOBJECT+TDNAME+TDID+TDSPRAS), si no existe, salta (esto también hace que la operación sea idempotente). Además: en SD_SALES_DOCU_MAINTAIN, el código de función para vaciar es 003 (DELETE_TEXT); pasar &&/005 solo reescribe el texto como una línea en blanco.
  • Al añadir una nueva condición de pricing, no se debe especificar el número de paso — Si se codifica COND_ST_NO='001' pero ZP01 está en STUNR=010, reporta «tipo de pricing no está en el número de paso 001». Déjalo vacío, que la secuencia de pricing lo localice automáticamente; usa «si la condición ya existe» para controlar UPDATEFLAG I/U.
  • Al redividir una interfaz, OBJ_KEY solo devuelve un número de documento ⇒ el número de documentos se subestima, y las líneas escritas señalan al documento incorrecto. El diseño de OBJ_KEY es número de documento(10)+sociedad(4)+año fiscal(4), el año está en +14(4) (a menudo mal escrito como +10(4) tomando la sociedad).
  • BSEG-BUZEI es NUMC(3), límite físico de 999 líneas por documento, el BAPI solo admite 990 líneas — El límite de fragmentación es 989 detalles + 1 compensación; el límite debe cubrir 999/1000/1997 (1997 juzgado como >999 se dividiría en 1000 líneas, causando desbordamiento).
  • BAPIACHE09 no tiene el campo EXCH_RATE — En documentos bimoneda (líneas 00+10), si solo la línea 00 lleva tipo de cambio, el sistema recalcula el tipo de cambio del encabezado según OB08 (poner 7 da 8.28). La línea con CURR_TYPE='10' también debe llevar exch_rate, tanto en la línea de detalle como en la línea de compensación.
  • En un documento bimoneda, cada ITEMNO_ACC debe tener dos entradas CURRENCYAMOUNT, y el subtotal de cada moneda debe ser 0 — Si falta la línea 10, reporta Inconsistent currency information; la línea de compensación generada en la división también debe completarse.
  • UMSKZ no tiene campo en ninguna de las tres estructuras BAPI — Las clases de efecto (W/S) que pasan por el canal BAPI estándar se descartan silenciosamente y la cuenta se registra incorrectamente. Solo se puede grabar a través de la mejora EXTENSION2, o usar todo el proceso POSTING_INTERFACE_* (entrada por lote FB01).
  • El parámetro NAME de READ_TEXT es TDOBNAME(70), el argumento real TDNAME(16) provoca un conflicto de tipos en tiempo de ejecución — Compila exitosamente, funciona con parámetros vacíos, pero al encontrar datos produce 500 Type conflict during a function module call. Declara el parámetro como TYPE tdobname, o lee directamente la tabla cluster STXL.

3.3 Texto / descripción / título de columna vacío

  • Un campo con tipo anónimo incorporado ⇒ DD03T tiene texto vacío ⇒ los títulos de columna en ALV están en blanco — El campo debe tener un elemento de datos (al asignarlo, que DD03T tenga 0 líneas es correcto, el texto se mueve hacia arriba). El texto de los campos de una estructura solo se puede definir mediante DDL, campo por campo, con @EndUserText.label; los campos de tipo STRU/TTYP anidados nunca tienen texto DD03T (los estándares de SAP también son así). El fields[].description de CreateStructure no se guarda en DD03T.
  • Elementos de texto inactivos / solo con versión inactiva ⇒ en tiempo de ejecución, TEXT-xxx siempre está vacío — Se manifiesta como títulos de columna ALV que retroceden al texto corto DDIC, y columnas de estado vacías.
  • Dominio que define tipo pero no mantiene valores ⇒ la pantalla de selección no puede desplegar F4 — Al consultar DD07T, debe incluir AS4LOCAL='A' (de lo contrario se obtienen líneas inactivas), ORDER BY VALPOS (de lo contrario se obtiene orden lexicográfico 1,10,11,2,3…), y el idioma debe retroceder al idioma maestro del dominio DD01L-DOMMASTER (el nombre del campo no es MASTERLANG).
  • MESSAGE ... WITH tiene un límite de aproximadamente 70 caracteres para el texto completo ⇒ Los errores largos de BAPI se truncan silenciosamente a Field Reas..., desviando la investigación. Usa un prefijo corto + plantilla de cadena con el texto original (≤220 caracteres, alineado con BAPI_MSG).
  • BAPIACGL09-ITEM_TEXT(50) / BAPIACHE09-HEADER_TXT(25) se truncan a la derecha silenciosamente — Al concatenar «prefijo+sufijo», primero trunca el prefijo a longitud - longitud del sufijo.
  • Los nombres de componentes DDIC se truncan silenciosamente a 30 caracteres — Al mapear nombres de clave JSON, algunas claves pueden desaparecer inexplicablemente.

3.4 Autorizaciones y filtros

  • AUTHORITY-CHECK con valor vacío generalmente resulta en «sin autorización» ⇒ los registros relacionados son permanentemente invisibles — El rol no suele conceder EKGRP = space. Cuando el campo de nivel organizativo está vacío, no se realiza la comprobación, se permite el paso directamente: IF lv_ekgrp IS NOT INITIAL. AUTHORITY-CHECK ... ENDIF.. Usa filtros a nivel de línea en lugar de rechazar toda la pantalla; de lo contrario, los usuarios sin autorización ni siquiera pueden entrar al código de transacción.
  • MODIF ID solo controla visibilidad/ocultación; los valores ya rellenados en campos ocultos siguen participando en el filtro — En escenarios de ocultación, usa RANGE para bloquear explícitamente en el lado de obtención de datos.

4. Transacciones y LUW

  • EXPORT ... TO DATABASE (log cluster) está controlado por la transacción — «Tabla subyacente → crear documento → fallo → rollback» hace que el log y la tabla subyacente estén en el mismo LUW; ROLLBACK también se lleva la instantánea de parámetros de entrada indx=01 (indx=02 permanece). Además, el FM de contabilización subyacente internamente hace BAPI_TRANSACTION_ROLLBACK, por lo que «eliminar tu propio ROLLBACK» o «cambiar el orden» no funciona. ⇒ Solución: Antes de la única acción de escritura en base de datos y antes de llamar al BAPI, inserta una sentencia independiente COMMIT WORK, así ROLLBACK conserva el log. Beneficio adicional: Hacer COMMIT del propio LUW antes de llamar al BAPI es una práctica estándar.
  • Si necesitas atomicidad, no hagas COMMIT por separado; si necesitas COMMIT independiente, mantén la relectura — SD_SALESDOCUMENT_CHANGE con CALL_FROM_BAPI='X' encola la actualización en la cola de actualización; sin COMMIT no se puede consultar ⇒ eliminar COMMIT WORK AND WAIT y mantener la verificación de relectura SELECT necesariamente juzgará erróneamente que «la bandera no se actualizó». En la rama de fallo de contabilización, debes añadir ROLLBACK WORK, de lo contrario la actualización SD pendiente quedará en el aire y se grabará incorrectamente en el próximo COMMIT.
  • La instantánea de la plataforma solo captura los parámetros del FM; las variables locales dentro de la función no se capturan — Los mensajes en lv_req/lv_resp no se ven en la plataforma; el serialize del monitor no incluye name_mappings, por lo que genera los nombres DDIC en mayúsculas en lugar de los nombres del contrato XML. El lado del negocio debe crear su propia tabla de log de mensajes y una línea de save( ) escrita en el único lugar donde se genera el mensaje (al final de frm_send), y un COMMIT independiente precisamente compensa la debilidad de la plataforma de que el log se pierde con el rollback.

5. Cuadros de diálogo / F4 / ALV / Pantalla de selección

  • La semántica de RETURNCODE en POPUP_GET_VALUES es contraintuitiva — 'A' = Cancelar; espacio = Confirmar y FIELDS se ha rellenado (LSPO4U01: IF ok_code_save = 'CANC'. returncode = 'A'. ELSE. fill_values. returncode = space.). Si escribes IF lv_rc <> 'A', tratarás «Confirmar» como «Cancelar» ⇒ el síntoma es hacer clic en Confirmar en la ventana emergente y no ocurre nada (sin mensaje, sin acción, sin volcado).
  • POPUP_GET_VALUES solo muestra F4 cuando el campo tiene una ayuda de búsqueda DDIC vinculada — El criterio es F4IF_DETERMINE_SEARCHHELP (0=tiene / 2=el campo existe pero no tiene ayuda / 1=el campo no existe). No intentes tomar prestada la ayuda cambiando a SKA1-SAKNR — introducirá el complemento ALPHA, convirtiendo '8000' en '000008000', la validación reportará errores y el formato de almacenamiento cambiará. ⇒ En su lugar, usa POPUP_GET_VALUES_USER_HELP + F4_FORMNAME + F4_PROGRAMNAME = sy-repid, sin cambiar los atributos del campo. El FORM de salida es llamado por el FM con 5 parámetros USING (tabname/fieldname/display_flag/returncode/value), debes escribir el valor seleccionado en el quinto parámetro y establecer returncode en 'F' para que la ventana emergente se actualice (no puedes obtener sy-stepl, no uses dynprofield para devolver).
  • POPUP_GET_VALUES no soporta ocultar campos por condición — SVAL solo puede controlar FIELD_ATTR (obligatorio/solo lectura). Carga solo el conjunto de campos correspondiente al tipo; si el usuario cambia de tipo, vuelve a mostrar la ventana según el nuevo tipo (si el tipo no cambia, sale, no hay bucle).
  • Al pasar FIELD_TAB a F4IF_INT_TABLE_VALUE_REQUEST, cada línea debe poder deducir INTTYPE — La cadena de deducción es TABNAME → INTTYPE → DATATYPE → DOMNAME → ROLLNAME. Si la deducción falla, el F4 no muestra datos. La solución es pre-llenar INTTYPE manualmente en FIELD_TAB.
  • Los eventos ALV (data_changed / user_command / hotspot_click) no se ejecutan si el layout de ALV no se ha creado correctamente — En particular, si falta SET PF-STATUS o SET TITLEBAR, los eventos de interacción del usuario no se reciben. Asegúrate de que el estado de GUI esté configurado y que el layout esté correctamente inicializado.
  • REUSE_ALV_GRID_DISPLAY modifica t_outtab en el lugar — Cualquier filtro, ordenación o modificación del usuario reordena la tabla de salida. Si necesitas la tabla original después de mostrar ALV, haz una copia de seguridad antes de la llamada.

6. Interfaces JSON y Gateway

  • La validación de JSON en ABAP no es robusta por defecto — cl_tcode_json=>deserialize no lanza errores para JSON mal formado; devuelve datos iniciales o vacíos. Siempre debes validar el JSON primero con cl_tcode_json=>validate o una biblioteca externa.
  • El orden de los campos en JSON no está garantizado — Al serializar/deserializar, el orden de los campos en la estructura ABAP puede no coincidir con el orden en JSON. Usa NAME_MAPPINGS para controlar la correspondencia.
  • Los nombres de campo ABAP (mayúsculas) no coinciden con los nombres de campo JSON (minúsculas/camelCase) — Sap debe usar NAME_MAPPINGS para mapear nombres. Sin mapeo, la serialización produce nombres DDIC en mayúsculas.
  • Las cadenas vacías en JSON se convierten en espacios en ABAP — Un campo JSON vacío ("") se convierte en un espacio en ABAP (' '). Si el negocio espera una cadena vacía (''), debes convertir manualmente los espacios a vacío después de la deserialización.
  • Las fechas en JSON (formato ISO YYYY-MM-DD) no se convierten automáticamente a formato SAP (YYYYMMDD) — Debes hacer la conversión manualmente.
  • Los campos numéricos en JSON con decimales pueden redondearse incorrectamente al convertirlos a tipos P o I de ABAP — La precisión y el escalado deben coincidir; de lo contrario, puede haber pérdida de datos.
  • /IWBEP/IF_MGW_* interfaces de Gateway requieren que los parámetros de entrada se pasen en estructuras específicas — No todas las llamadas de función aceptan parámetros de tabla; algunas esperan estructuras anidadas.

7. Creación de objetos DDIC

  • CreateStructure no establece DD03T — Los textos de campo de la estructura no se escriben. Después de crear la estructura, debes leer DD03T y escribir los textos manualmente a través de DDUT_DD03T_MAINTAIN o similar.
  • CreateDataElement / CreateDomain requieren que el dominio y el elemento de datos se activen por separado — Los objetos DDIC no se activan implícitamente al crearlos. Debes llamar explícitamente a la activación.
  • Los nombres de dominio y elemento de datos deben ser únicos globalmente — Si el nombre ya existe (aunque esté inactivo), la operación falla. Consulta primero DD01L/DD04L con AS4LOCAL='A'.
  • Al modificar un dominio existente, debes considerar todas las referencias — Cambiar el tipo de datos o la longitud de un dominio puede afectar a todos los elementos de datos y campos de tabla que lo usan. El sistema puede no permitir la modificación si hay dependencias activas.
  • Los catálogos de búsqueda (search helps) se crean y activan por separado — No están vinculados a la estructura o tabla, se asignan mediante DD35V o en el mantenimiento de la tabla.

8. ST22 y técnicas de diagnóstico con sondas

  • ST22 es el primer paso ante cualquier volcado corto — El Error analysis contiene la ubicación exacta del error (Program, Include, Line) y el tipo de excepción (CX_SY_*), mucho más rápido que revisar FORM por FORM.
  • Usa sondas de solo lectura para aislar el problema — Crea un programa $TMP o Z que llame a las funciones sospechosas con parámetros fijos y verifique los resultados. No modifiques el programa original hasta que la causa raíz esté clara.
  • El log de la aplicación (SLG0/SLG1) puede estar desactivado — Si tu función usa BAL_LOG_CREATE pero no hay logs, verifica la activación del objeto de log.
  • Los puntos de interrupción (breakpoints) en programas de producción pueden ser eliminados por el sistema — En sistemas productivos, los puntos de interrupción externos pueden ser rechazados. Usa BREAK-POINT solo en sistemas de desarrollo/calidad.
  • La transacción SAT (Análisis de tiempo de ejecución) es mejor que SE30 — SAT proporciona más detalles sobre el rendimiento, incluyendo el tiempo de base de datos y el tiempo de aplicación.

9. Trampas en scripts de parches

  • Los scripts que modifican código fuente en producción deben ejecutarse en modo de prueba primero — Usa la funcionalidad de «simulación» o «test run» si está disponible; de lo contrario, ejecuta el script en un sistema de calidad primero.
  • Los scripts que eliminan objetos (DELETE TRANSPORT) deben verificar primero que el objeto no esté en uso — Eliminar un objeto que todavía es referenciado por otro puede causar errores en tiempo de ejecución.
  • Los scripts que mueven objetos entre paquetes deben verificar los permisos — Mover un objeto a un paquete diferente puede requerir autorizaciones especiales.
  • Los scripts de parche que modifican elementos de texto deben primero leer la versión activa — Como se mencionó en la regla de oro #2, get_source no incluye TEXTPOOL; debes leer los elementos de texto activos antes de sobrescribir.
  • Los scripts que ejecutan RS_CUA_INTERNAL_FETCH/_WRITE deben manejar correctamente las excepciones — El estado 'A' (activo) es crítico; si el objeto GUI no está activo, la escritura falla silenciosamente.

10. Búsqueda rápida por síntoma

SíntomaSección a consultar
«Compila pero la activación falla»§1 (ADT/MCP) y §2 (Sintaxis)
«Activación exitosa pero el programa se comporta incorrectamente»§1 (Falsa activación) y §3 (Fallo silencioso)
«Retorna S pero los datos no cambian»§3.2 (BAPI) y §3.4 (Autorizaciones)
«El texto del error no coincide con la causa real»§0 (Meta-regla 1) y §2 (Sintaxis)
«Botón no responde / F4 no se abre»§5 (Diálogo/F4)
«Datos desplazados / perdidos»§3.1 (Desplazamiento)
«ST22: Excepción CX_SY_*»§8 (ST22)
«El script de parche no funciona»§9 (Scripts)
«Problemas con BAPI SD»§3.2 (BAPI SD) y §4 (Transacciones)
«Problemas de interfaz JSON»§6 (JSON/Gateway)

11. Checklist de inicio de proyecto

Fase 1: Evaluación (Antes de empezar)

  • Leer la sección 0 (Diez reglas de oro) de este skill.
  • Revisar el resumen general del proyecto (sap-abap-project-ops) para entornos, solicitudes y objetos.
  • Configurar el acceso a los sistemas (desarrollo, calidad, producción).
  • Verificar que las autorizaciones necesarias están concedidas.

Fase 2: Pre-implementación (Antes de escribir código)

  • Identificar los objetos existentes que se modificarán. get_source de cada uno y guardar copia en backup/.
  • Verificar bloqueos en los objetos que se van a modificar (SM12).
  • Leer los elementos de texto activos (read_textelements) y guardar copia.
  • Confirmar los números de solicitud con el usuario.

Fase 3: Autocomprobación de codificación

  • Seguir las reglas de sintaxis de §2 para evitar errores de compilación.
  • Para BAPI/funciones estándar, revisar §3.2 para evitar fallos silenciosos.
  • Después de modificar un objeto, activar y verificar con ?version=active e inactive.
  • Realizar una conciliación con la base de datos después de operaciones de escritura (SELECT de verificación).

Fase 4: Entrega

  • Generar el objeto.
  • Subir código fuente.
  • Subir y activar elementos de texto (PROG/PX por separado).
  • Generar estado GUI (CUA) si es necesario.
  • Verificar que el objeto funciona en el sistema de calidad.

Fase 5: Cierre

  • Eliminar objetos de prueba/sonda (después de get_source y respaldo).
  • Verificar que TADIR está limpio.
  • Actualizar la documentación del proyecto.
  • Comunicar al usuario que la entrega está completa.

Este skill se actualiza continuamente a partir de la experiencia en proyectos reales. Las nuevas entradas se añaden a references/casebook.md y se resumen aquí.

Skills relacionados