En Classic ABAP, los Reports son la base del desarrollo de aplicaciones. REPORT, PARAMETERS, SELECT-OPTIONS y WRITE - con esto se desarrolló durante décadas. En ABAP Cloud el mundo es diferente: Sin Reports clásicos, sin Dynpros, sin Selection Screens. En su lugar: Executable Classes, Application Jobs y Fiori Apps.
El problema: ¿Reports en ABAP Cloud?
Quien quiera crear un Report clásico en ABAP Cloud se decepcionará:
" ❌ No es posible en ABAP Cloud:REPORT zflight_report.PARAMETERS: p_carr TYPE s_carr_id.START-OF-SELECTION. SELECT * FROM sflight INTO TABLE @DATA(lt_flights) WHERE carrid = @p_carr. LOOP AT lt_flights INTO DATA(ls_flight). WRITE: / ls_flight-carrid, ls_flight-connid. ENDLOOP.Este código será rechazado por el compilador - REPORT, PARAMETERS y WRITE no son sentencias válidas de ABAP Cloud.
Las alternativas en resumen
ABAP Cloud ofrece tres enfoques principales para la ejecución de programas:
┌─────────────────────────────────────────────────────────────────┐│ Programas en ABAP Cloud │├─────────────────────────────────────────────────────────────────┤│ ││ 1. Executable Class (if_oo_adt_classrun) ││ → Tests rápidos, desarrollo, debug ││ → Ejecución directa en ADT ││ ││ 2. Application Job ││ → Procesamiento en segundo plano planificado ││ → Parámetros vía catálogo de jobs ││ → Monitoreo en Fiori ││ ││ 3. Fiori App (basada en RAP) ││ → Interfaz de usuario interactiva ││ → Filtros y Actions ││ → UX completa ││ │└─────────────────────────────────────────────────────────────────┘Executable Classes con IF_OO_ADT_CLASSRUN
El interface IF_OO_ADT_CLASSRUN permite la ejecución directa de una clase en los ABAP Development Tools (ADT). Es la forma más rápida de ejecutar y probar código.
Estructura básica
CLASS zcl_flight_report DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_flight_report IMPLEMENTATION. METHOD if_oo_adt_classrun~main. " Aquí va la lógica del programa out->write( 'Report de reservas de vuelos' ). out->write( '============================' ).
" Cargar datos SELECT FROM zflight_book FIELDS booking_id, flight_id, customer_id, booking_date, price INTO TABLE @DATA(lt_bookings).
" Salida out->write( lt_bookings ). ENDMETHOD.ENDCLASS.Ejecución en ADT
Clic derecho en clase → Run As → ABAP Application (Console)→ La salida aparece en la vista ConsoleReport de reserva de vuelos: Ejemplo completo
CLASS zcl_booking_analysis DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_oo_adt_classrun.
PRIVATE SECTION. TYPES: BEGIN OF ty_summary, carrier_id TYPE s_carr_id, total_bookings TYPE i, total_revenue TYPE p LENGTH 15 DECIMALS 2, avg_price TYPE p LENGTH 10 DECIMALS 2, END OF ty_summary, tt_summary TYPE STANDARD TABLE OF ty_summary WITH EMPTY KEY.
METHODS analyze_bookings RETURNING VALUE(rt_summary) TYPE tt_summary.
METHODS get_top_routes RETURNING VALUE(rt_routes) TYPE string_table.
ENDCLASS.
CLASS zcl_booking_analysis IMPLEMENTATION. METHOD if_oo_adt_classrun~main. out->write( |Análisis de reservas de vuelos - { cl_abap_context_info=>get_system_date( ) }| ). out->write( |Creado por: { cl_abap_context_info=>get_user_technical_name( ) }| ). out->write( '=' && repeat( val = '=' occ = 50 ) ). out->write( `` ).
" Resumen de reservas por aerolínea out->write( '1. Reservas por aerolínea:' ). out->write( '-' && repeat( val = '-' occ = 40 ) ).
DATA(lt_summary) = analyze_bookings( ). LOOP AT lt_summary INTO DATA(ls_summary). out->write( | { ls_summary-carrier_id }: { ls_summary-total_bookings } reservas, | && |Ingresos: { ls_summary-total_revenue } EUR, | && |Promedio: { ls_summary-avg_price } EUR| ). ENDLOOP. out->write( `` ).
" Rutas más populares out->write( '2. Rutas más populares:' ). out->write( '-' && repeat( val = '-' occ = 40 ) ). DATA(lt_routes) = get_top_routes( ). LOOP AT lt_routes INTO DATA(lv_route). out->write( | { sy-tabix }. { lv_route }| ). ENDLOOP.
out->write( `` ). out->write( 'Análisis completado.' ). ENDMETHOD.
METHOD analyze_bookings. SELECT FROM zflight_book AS b INNER JOIN zflight AS f ON b~flight_id = f~flight_id FIELDS f~carrier_id, COUNT( * ) AS total_bookings, SUM( b~price ) AS total_revenue, AVG( b~price ) AS avg_price GROUP BY f~carrier_id ORDER BY total_bookings DESCENDING INTO TABLE @rt_summary. ENDMETHOD.
METHOD get_top_routes. SELECT FROM zflight_book AS b INNER JOIN zflight AS f ON b~flight_id = f~flight_id FIELDS f~carrier_id, f~connection_id, f~departure_city, f~arrival_city, COUNT( * ) AS booking_count GROUP BY f~carrier_id, f~connection_id, f~departure_city, f~arrival_city ORDER BY booking_count DESCENDING UP TO 5 ROWS INTO TABLE @DATA(lt_routes).
LOOP AT lt_routes INTO DATA(ls_route). APPEND |{ ls_route-departure_city } → { ls_route-arrival_city } | && |({ ls_route-carrier_id }/{ ls_route-connection_id }): | && |{ ls_route-booking_count } reservas| TO rt_routes. ENDLOOP. ENDMETHOD.ENDCLASS.Comparación: REPORT vs. Executable Class
| Aspecto | REPORT clásico | Executable Class |
|---|---|---|
| Creación | REPORT zreport. | Clase con Interface |
| Parámetros | PARAMETERS, SELECT-OPTIONS | Ninguno (hardcoded o tabla) |
| Salida | WRITE, ALV, Dynpro | out->write( ) |
| Ejecución | SE38, SA38, Transacción | ADT Console |
| Scheduling | SM36, Background Job | Application Job |
| Testing | Manual | Unit Tests posibles |
Application Jobs: Procesamiento en segundo plano
Para ejecuciones programadas y procesamiento en segundo plano, ABAP Cloud ofrece Application Jobs. Estos reemplazan los Background Jobs clásicos de SM36/SM37.
Componentes de un Application Job
┌─────────────────────────────────────────────────────────────────┐│ Application Job │├─────────────────────────────────────────────────────────────────┤│ ││ 1. Job Catalog Entry ││ → Registra el job en el sistema ││ → Define interface de parámetros ││ ││ 2. Job Template (opcional) ││ → Valores de parámetros predefinidos ││ → Reutilizable ││ ││ 3. Job Class ││ → Implementa IF_APJ_DT_EXEC_OBJECT ││ → Contiene lógica de negocio ││ ││ 4. Scheduling ││ → Fiori App "Schedule Application Jobs" ││ → Único, recurrente, basado en eventos ││ │└─────────────────────────────────────────────────────────────────┘Implementar clase de Job
CLASS zcl_booking_cleanup_job DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_apj_dt_exec_object.
PRIVATE SECTION. DATA mv_days_to_keep TYPE i. DATA mv_dry_run TYPE abap_bool.
METHODS delete_old_bookings RETURNING VALUE(rv_deleted) TYPE i.
METHODS log_result IMPORTING iv_deleted TYPE i.
ENDCLASS.
CLASS zcl_booking_cleanup_job IMPLEMENTATION.
METHOD if_apj_dt_exec_object~get_parameters. " Definir parámetros para el catálogo de jobs et_parameter_def = VALUE #( ( selname = 'P_DAYS' kind = if_apj_dt_exec_object=>parameter datatype = 'INT4' length = 10 param_text = 'Período de retención (días)' changeable_ind = abap_true mandatory_ind = abap_true )
( selname = 'P_DRYRUN' kind = if_apj_dt_exec_object=>parameter datatype = 'CHAR' length = 1 param_text = 'Prueba (X = sí)' changeable_ind = abap_true mandatory_ind = abap_false ) ).
" Valores por defecto et_parameter_val = VALUE #( ( selname = 'P_DAYS' low = '90' ) ( selname = 'P_DRYRUN' low = 'X' ) ). ENDMETHOD.
METHOD if_apj_dt_exec_object~execute. " Leer parámetros mv_days_to_keep = VALUE #( it_parameters[ selname = 'P_DAYS' ]-low DEFAULT 90 ). mv_dry_run = xsdbool( VALUE #( it_parameters[ selname = 'P_DRYRUN' ]-low DEFAULT '' ) = 'X' ).
" Ejecución DATA(lv_deleted) = delete_old_bookings( ).
" Logging log_result( lv_deleted ). ENDMETHOD.
METHOD delete_old_bookings. DATA(lv_cutoff_date) = cl_abap_context_info=>get_system_date( ) - mv_days_to_keep.
IF mv_dry_run = abap_true. " Solo contar SELECT COUNT( * ) FROM zflight_book WHERE booking_status = 'C' " Cancelled AND booking_date < @lv_cutoff_date INTO @rv_deleted. ELSE. " Realmente eliminar DELETE FROM zflight_book WHERE booking_status = 'C' AND booking_date < @lv_cutoff_date. rv_deleted = sy-dbcnt. ENDIF. ENDMETHOD.
METHOD log_result. " Usar Application Log DATA(lo_log) = cl_bali_log=>create_with_header( header = cl_bali_header_setter=>create( object = 'ZFB_LOG' subobject = 'CLEANUP' ) ).
DATA(lv_msg) = COND #( WHEN mv_dry_run = abap_true THEN |Prueba: { iv_deleted } reservas serían eliminadas.| ELSE |{ iv_deleted } reservas antiguas fueron eliminadas.| ).
lo_log->add_item( cl_bali_free_text_setter=>create( text = lv_msg ) ). cl_bali_log_db=>get_instance( )->save_log( log = lo_log ). ENDMETHOD.
ENDCLASS.Crear entrada de catálogo de Job
El Job debe registrarse en el Job Catalog. En ADT:
Clic derecho en paquete → New → Other ABAP Repository Object→ Application Jobs → Application Job Catalog EntryNombre: ZFB_BOOKING_CLEANUPDescripción: Eliminar reservas canceladas antiguasJob-Class: ZCL_BOOKING_CLEANUP_JOBCrear Job-Template (opcional)
Nombre: ZFB_CLEANUP_WEEKLYEntrada de catálogo: ZFB_BOOKING_CLEANUPParámetros: P_DAYS = 30 P_DRYRUN = (vacío)Planificar y monitorear Jobs
La planificación se realiza a través de la Fiori App Schedule Application Jobs:
| Función | App-ID | Descripción |
|---|---|---|
| Planificar | F2640 | Programar jobs |
| Monitorear | F2079 | Jobs en ejecución/completados |
| Logs | F0399 | Application Log |
¿Cuándo Fiori App vs. Application Job?
La elección entre Fiori App y Application Job depende del caso de uso:
┌─────────────────────────────────────────────────────────────────┐│ Matriz de decisión │├─────────────────────────────────────────────────────────────────┤│ ││ ¿Se requiere interacción del usuario? ││ ├── Sí → Fiori App (basada en RAP) ││ │ • Establecer filtros ││ │ • Editar datos ││ │ • Feedback inmediato ││ │ ││ └── No → Application Job ││ • Ejecución programada ││ • Larga duración ││ • Procesamiento batch ││ ││ ¿Tiempo de ejecución > 60 segundos? ││ ├── Sí → Application Job o bgPF ││ └── No → Posible síncronamente ││ │└─────────────────────────────────────────────────────────────────┘Ejemplos para el enfoque correcto
| Requisito | Enfoque recomendado |
|---|---|
| Mostrar resumen de reservas | Fiori Elements List Report |
| Crear nueva reserva | Fiori Object Page |
| Crear estadísticas diarias | Application Job |
| Limpieza mensual | Application Job |
| Generar informe PDF | bgPF (RAP Action asíncrona) |
| Análisis de datos ad-hoc | Executable Class |
Ejemplo de reserva de vuelos: Programa completo
Aquí un ejemplo completo de un “programa” ABAP Cloud que combina diferentes enfoques:
1. Executable Class para desarrollo/test
CLASS zcl_flight_util DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_oo_adt_classrun.
" Métodos reutilizables METHODS get_bookings_by_carrier IMPORTING iv_carrier TYPE s_carr_id RETURNING VALUE(rt_bookings) TYPE ztt_flight_booking.
METHODS calculate_revenue IMPORTING it_bookings TYPE ztt_flight_booking RETURNING VALUE(rv_revenue) TYPE p LENGTH 15 DECIMALS 2.
ENDCLASS.
CLASS zcl_flight_util IMPLEMENTATION. METHOD if_oo_adt_classrun~main. " Ejecución de prueba out->write( 'Utilidades de reserva de vuelos - Test' ).
DATA(lt_lh_bookings) = get_bookings_by_carrier( 'LH' ). out->write( |Lufthansa: { lines( lt_lh_bookings ) } reservas| ).
DATA(lv_revenue) = calculate_revenue( lt_lh_bookings ). out->write( |Ingresos: { lv_revenue } EUR| ). ENDMETHOD.
METHOD get_bookings_by_carrier. SELECT FROM zflight_book AS b INNER JOIN zflight AS f ON b~flight_id = f~flight_id FIELDS b~booking_id, b~flight_id, b~customer_id, b~booking_date, b~price, b~currency_code WHERE f~carrier_id = @iv_carrier INTO TABLE @rt_bookings. ENDMETHOD.
METHOD calculate_revenue. rv_revenue = REDUCE #( INIT sum = CONV p( 0 ) FOR booking IN it_bookings NEXT sum = sum + booking-price ). ENDMETHOD.ENDCLASS.2. Application Job para procesamiento batch
CLASS zcl_flight_stats_job DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES if_apj_dt_exec_object.
ENDCLASS.
CLASS zcl_flight_stats_job IMPLEMENTATION. METHOD if_apj_dt_exec_object~get_parameters. et_parameter_def = VALUE #( ( selname = 'P_PERIOD' kind = if_apj_dt_exec_object=>parameter datatype = 'CHAR' length = 7 param_text = 'Período (AAAAMM)' mandatory_ind = abap_true ) ).
" Default: Mes anterior DATA(lv_date) = cl_abap_context_info=>get_system_date( ) - 30. et_parameter_val = VALUE #( ( selname = 'P_PERIOD' low = |{ lv_date(6) }| ) ). ENDMETHOD.
METHOD if_apj_dt_exec_object~execute. DATA(lv_period) = VALUE #( it_parameters[ selname = 'P_PERIOD' ]-low DEFAULT '' ).
" Calcular y guardar estadísticas DATA(lo_util) = NEW zcl_flight_util( ).
" ... Lógica de procesamiento ... ENDMETHOD.ENDCLASS.3. RAP Service para Fiori App
La interfaz interactiva se realiza a través de RAP:
" CDS View para visualización de reservas@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Reservas de vuelos'define root view entity ZC_FlightBooking provider contract transactional_query as projection on ZI_FlightBooking{ @UI.facet: [{ type: #IDENTIFICATION_REFERENCE }]
@UI.lineItem: [{ position: 10 }] @UI.identification: [{ position: 10 }] key BookingId,
@UI.lineItem: [{ position: 20 }] FlightId,
@UI.lineItem: [{ position: 30 }] @UI.selectionField: [{ position: 10 }] CustomerId,
@UI.lineItem: [{ position: 40 }] @UI.selectionField: [{ position: 20 }] BookingDate,
@UI.lineItem: [{ position: 50 }] Price,
CurrencyCode, BookingStatus}Mejores prácticas
1. Estructura tu código
" ✅ Bueno: Lógica en métodos separadosCLASS zcl_booking_report DEFINITION. PUBLIC SECTION. INTERFACES if_oo_adt_classrun. PRIVATE SECTION. METHODS fetch_data RETURNING VALUE(rt_data) TYPE tt_data. METHODS process_data IMPORTING it_data TYPE tt_data. METHODS output_results IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.ENDCLASS.
" ❌ Malo: Todo en mainCLASS zcl_bad_example IMPLEMENTATION. METHOD if_oo_adt_classrun~main. " 500 líneas de código aquí... ENDMETHOD.ENDCLASS.2. Usa información de contexto
" En lugar de SY-UNAME, SY-DATUM etc.DATA(lv_user) = cl_abap_context_info=>get_user_technical_name( ).DATA(lv_date) = cl_abap_context_info=>get_system_date( ).DATA(lv_time) = cl_abap_context_info=>get_system_time( ).Detalles sobre esto en el artículo Campos de sistema en ABAP Cloud.
3. Usar Application Logging
" En lugar de WRITE para output de JobDATA(lo_log) = cl_bali_log=>create_with_header( header = cl_bali_header_setter=>create( object = 'ZFB_LOG' subobject = 'REPORTS' )).lo_log->add_item( cl_bali_free_text_setter=>create( text = 'Procesamiento iniciado' ) ).cl_bali_log_db=>get_instance( )->save_log( log = lo_log ).4. Considerar testabilidad con Unit Tests
" ¡La clase con if_oo_adt_classrun también es testeable con unit tests!CLASS ltc_flight_util DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. PRIVATE SECTION. METHODS test_calculate_revenue FOR TESTING.ENDCLASS.
CLASS ltc_flight_util IMPLEMENTATION. METHOD test_calculate_revenue. DATA(lo_cut) = NEW zcl_flight_util( ). DATA(lt_bookings) = VALUE ztt_flight_booking( ( price = 100 ) ( price = 200 ) ). cl_abap_unit_assert=>assert_equals( act = lo_cut->calculate_revenue( lt_bookings ) exp = 300 ). ENDMETHOD.ENDCLASS.Conclusión
Los programas en ABAP Cloud requieren un cambio de mentalidad:
| Classic ABAP | ABAP Cloud |
|---|---|
REPORT | Executable Class |
PARAMETERS | Job-Parameters / Fiori Filter |
WRITE | out->write( ) / Application Log |
| SM36/SM37 | Application Jobs (Fiori) |
| Transacción | Fiori App |
El cambio de Reports a clases promueve un mejor diseño: Métodos reutilizables, testabilidad con unit tests y clara separación de lógica y salida.
Para procesamiento asíncrono ver RAP con bgPF, para la decisión Fiori Elements vs. Freestyle ver Fiori Elements vs. Freestyle. Los fundamentos de migración los encontrarás en la Guía de migración ABAP Cloud.