Documentos de Académico
Documentos de Profesional
Documentos de Cultura
#Abap
#Abap
#abap
Tabla de contenido
Acerca de 1
Observaciones 2
Versiones 2
Examples 2
Hola Mundo 2
Examples 4
Examples 7
Instrucción SELECT 7
Capítulo 4: Bucles 8
Observaciones 8
Examples 8
Mientras bucle 8
Hacer bucle 9
Comandos generales 9
Introducción 11
Observaciones 11
Examples 11
Definiendo una clase de mensaje 11
Mensajería Dinámica 12
Capítulo 6: Comentarios 13
Examples 13
Fin de la línea 13
Línea completa 13
Sintaxis 14
Examples 14
Variable local 14
Variable global 14
Examples 15
Examples 17
IF / ELSEIF / ELSE 17
CASO 17
COMPROBAR 17
AFIRMAR 17
COND / INTERRUPTOR 18
COND 18
Ejemplos 18
CAMBIAR 18
Ejemplos 18
Capítulo 10: Examen de la unidad 20
Examples 20
Examples 22
Reemplazo 22
buscando 22
Examples 24
Literales 24
Plantillas de cadena 24
Cuerdas de concatenacion 24
Examples 26
Declaración de clase 26
Las clases ABAP pueden ser declaradas global o localmente . Una clase global puede ser uti 26
Constructor, metodos 26
Herencia - definición 28
Información 28
Implementación de clase 28
Información 28
Implementación de la clase: 28
Sintaxis 30
Examples 30
Examples 32
Simbolos de campo 32
Referencias de datos 33
Examples 35
Creditos 38
Acerca de
You can share this PDF with anyone you feel could benefit from it, downloaded the latest version
from: abap
It is an unofficial and free ABAP ebook created for educational purposes. All the content is
extracted from Stack Overflow Documentation, which is written by many hardworking individuals at
Stack Overflow. It is neither affiliated with Stack Overflow nor official ABAP.
The content is released under Creative Commons BY-SA, and the list of contributors to each
chapter are provided in the credits section at the end of this book. Images may be copyright of
their respective owners unless otherwise specified. All trademarks and registered trademarks are
the property of their respective company owners.
Use the content presented in this book at your own risk; it is not guaranteed to be correct nor
accurate, please send your feedback and corrections to info@zzzprojects.com
https://riptutorial.com/es/home 1
Capítulo 1: Empezando con ABAP
Observaciones
ABAP es un lenguaje de programación desarrollado por SAP para la programación de
aplicaciones empresariales en el entorno SAP.
Versiones
Examples
Hola Mundo
PROGRAM zhello_world.
START-OF-SELECTION.
WRITE 'Hello, World!'.
En lugar de imprimir en la consola, ABAP escribe valores en una lista que se mostrará tan pronto
como se ejecutó la lógica principal.
https://riptutorial.com/es/home 2
Hola mundo en objetos ABAP
PROGRAM zhello_world.
START-OF-SELECTION.
main=>start( ).
https://riptutorial.com/es/home 3
Capítulo 2: ABAP GRID List Viewer (ALV)
Examples
Creación y visualización de un ALV
Este ejemplo muestra la creación ALV más simple utilizando la clase cl_salv_table y sin opciones
de formato adicionales. Se incluirían opciones de formato adicionales después del bloque TRY
ENDTRY y antes de la llamada del método alv->display( ) .
Todos los ejemplos posteriores que utilicen el enfoque de Objetos ABAP para la creación de ALV
utilizarán este ejemplo como punto de partida.
" Fill ALV object with data from the internal table
TRY.
cl_salv_table=>factory(
IMPORTING
r_salv_table = alv
CHANGING
t_table = t_spfli ).
CATCH cx_salv_msg INTO error_message.
" error handling
ENDTRY.
" Use the ALV object's display method to show the ALV on the screen
alv->display( ).
Este ejemplo muestra cómo optimizar el ancho de columna para que los encabezados de
columna y los datos no se recorten.
alv->get_columns( )->set_optimize( ).
Este ejemplo oculta el campo MANDT (cliente) de la ALV. Tenga en cuenta que el parámetro pasado
a get_column( ) debe estar en mayúsculas para que esto funcione.
https://riptutorial.com/es/home 4
El texto de la columna puede cambiar al cambiar el tamaño horizontal de una columna. Hay tres
métodos para lograr esto:
set_short_text 10
set_medium_text 20
set_long_text 40
El siguiente ejemplo muestra el uso de los tres. Un objeto de column se declara y crea una
instancia como referencia al resultado de alv->get_columns( )->get_column( 'DISTID' ) . El nombre
de la columna debe estar en mayúsculas. Esto es así para que este método de encadenamiento
solo se llame una vez en su instanciación, en lugar de ejecutarse cada vez que se cambie un
encabezado de columna.
alv->get_functions( )->set_all( ).
Este método aumenta la legibilidad al dar filas consecutivas que alternan el sombreado del color
de fondo.
https://riptutorial.com/es/home 5
Lea ABAP GRID List Viewer (ALV) en línea: https://riptutorial.com/es/abap/topic/4660/abap-grid-
list-viewer--alv-
https://riptutorial.com/es/home 6
Capítulo 3: Abrir SQL
Examples
Instrucción SELECT
SELECT es una declaración Open-SQL para leer datos de una o varias tablas de base de datos
en objetos de datos .
* This returns single record if table consists multiple records with same key.
SELECT SINGLE * INTO TABLE lt_mara
FROM mara
WHERE matnr EQ '400-500'.
4. Funciones agregadas
* This puts the number of records present in table MARA into the variable lv_var
SELECT COUNT( * ) FROM mara
INTO lv_var.
https://riptutorial.com/es/home 7
Capítulo 4: Bucles
Observaciones
Cuando se realiza un bucle sobre tablas internas, generalmente es preferible ASSIGN a un símbolo
de campo en lugar de un bucle INTO un área de trabajo. La asignación de símbolos de campo
simplemente actualiza su referencia para apuntar a la siguiente línea de la tabla interna durante
cada iteración, mientras que el uso de INTO hace que la línea de la tabla se copie en el área de
trabajo, lo que puede ser costoso para las tablas largas / anchas.
Examples
Lazo de tabla interno
Bucle condicional
Si solo las líneas que coinciden con una determinada condición deben incluirse en el bucle, la
adición WHERE se puede agregar.
Mientras bucle
ABAP también ofrece el WHILE -Loop convencional que se ejecuta hasta que la expresión dada se
evalúa como falsa. El sy-index sistema del campo del sistema se incrementará para cada paso del
bucle.
WHILE condition.
* do something
ENDWHILE
https://riptutorial.com/es/home 8
Hacer bucle
Sin ninguna adición, el DO Loop se ejecuta sin fin o al menos hasta que sale explícitamente del
interior. El sy-index sistema del campo del sistema se incrementará para cada paso del bucle.
DO.
* do something... get it?
* call EXIT somewhere
ENDDO.
La adición de TIMES ofrece una manera muy conveniente de repetir el código (la amount representa
un valor de tipo i ).
DO amount TIMES.
* do several times
ENDDO.
Comandos generales
DO.
READ TABLE itab INDEX sy-index INTO DATA(wa).
IF sy-subrc <> 0.
EXIT. "Stop this loop if no element was found
ENDIF.
" some code
ENDDO.
Para saltar al siguiente paso del bucle, se puede usar el comando CONTINUE .
DO.
IF sy-index MOD 1 = 0.
CONTINUE. " continue to next even index
ENDIF.
" some code
ENDDO.
La sentencia CHECK es un CONTINUE con condición. Si la condición resulta ser falsa , se ejecutará
CONTINUE . En otras palabras: el bucle solo continuará con el paso si la condición es verdadera .
DO.
" some code
CHECK sy-index < 10.
" some code
ENDDO.
https://riptutorial.com/es/home 9
DO.
" some code
IF sy-index >= 10.
CONTINUE.
ENDIF.
" some code
ENDDO.
https://riptutorial.com/es/home 10
Capítulo 5: Clases de mensajes / palabra
clave MESSAGE
Introducción
La instrucción MESSAGE se puede usar para interrumpir el flujo del programa y mostrar mensajes
cortos al usuario. El contenido de los mensajes se puede definir en el código del programa, en los
símbolos de texto del programa o en una clase de mensaje independiente definida en SE91 .
Observaciones
La longitud máxima de un mensaje, incluidos los parámetros que se le pasan con & , es de 72
caracteres.
Examples
Definiendo una clase de mensaje
PROGRAM zprogram MESSAGE-ID sabapdemos.
El mensaje definido por el sistema se puede almacenar en una clase de mensaje. El token
MESSAGE-ID define la clase de mensaje sabapdemos para todo el programa. Si no se usa, la clase de
mensaje debe especificarse en cada llamada de MESSAGE .
Un mensaje mostrará el texto almacenado en el símbolo de texto i00 al usuario. Dado que el tipo
de mensaje es i (como se ve en i000 ), después de que el usuario salga del cuadro de diálogo, el
flujo del programa continuará desde el punto de la llamada de MESSAGE .
PROGRAM zprogram.
...
MESSAGE i050(sabapdemos).
Puede ser inconveniente definir una clase de mensaje para todo el programa, por lo que es
posible definir la clase de mensaje de la que proviene el mensaje en la propia declaración MESSAGE
https://riptutorial.com/es/home 11
. Este ejemplo mostrará el mensaje 050 de la clase de mensaje sabapdemos .
Mensajería Dinámica
El símbolo & se puede usar en un mensaje para permitir que se le pasen parámetros.
Parámetros ordenados
Llamar a este mensaje con tres parámetros devolverá un mensaje usando los parámetros:
Este mensaje se mostrará como Message with type E 010 in event START-OF-SELECTION . El número
junto al símbolo & designa el orden en que se muestran los parámetros.
Parámetros desordenados
https://riptutorial.com/es/home 12
Capítulo 6: Comentarios
Examples
Fin de la línea
Cualquier texto que sigue a un " carácter en la misma línea está comentado:
Línea completa
El * carácter comenta una línea completa. El * debe ser el primer carácter en la línea.
https://riptutorial.com/es/home 13
Capítulo 7: Convenciones de nombres
Sintaxis
• Los caracteres, números y _ se pueden usar para el nombre de variable.
• Dos caracteres utilizando para estado variable y tipo de objeto.
• Las variables locales comienzan con L.
• Las variables globales comienzan con G.
• El parámetro de entrada de función comienza con I (importar).
• El parámetro de salida de la función comienza con E (exportar).
• El símbolo de las estructuras es S.
• El símbolo de la mesa es T.
Examples
Variable local
Variable global
https://riptutorial.com/es/home 14
Capítulo 8: Declaración de datos
Examples
Declaración de datos en línea
Cuando se utiliza una declaración de datos en línea dentro de un bloque SELECT...ENDSELECT o una
SELECT SINGLE , el carácter @ debe usarse como un carácter de escape para la expresión
DATA(lv_cityto) . Una vez que se ha utilizado el carácter de escape, todas las demás variables del
host también deben escaparse (como es el caso con lv_carrid continuación).
Salidas BERLIN .
https://riptutorial.com/es/home 15
Lea Declaración de datos en línea: https://riptutorial.com/es/abap/topic/1646/declaracion-de-datos
https://riptutorial.com/es/home 16
Capítulo 9: Declaraciones de flujo de control
Examples
IF / ELSEIF / ELSE
IF lv_foo = 3.
WRITE: / 'lv_foo is 3'.
ELSEIF lv_foo = 5.
WRITE: / 'lv_foo is 5'.
ELSE.
WRITE: / 'lv_foo is neither 3 nor 5'.
ENDIF.
CASO
CASE lv_foo.
WHEN 1.
WRITE: / 'lv_foo is 1'.
WHEN 2.
WRITE: / 'lv_foo is 2'.
WHEN 3.
WRITE: / 'lv_foo is 3'.
WHEN OTHERS.
WRITE: / 'lv_foo is something else'.
ENDCASE
COMPROBAR
CHECKes una declaración simple que evalúa una expresión lógica y sale del bloque de
procesamiento actual si es falso.
METHOD do_something.
CHECK iv_input IS NOT INITIAL. "Exits method immediately if iv_input is initial
AFIRMAR
ASSERT se utiliza en áreas sensibles donde desea estar absolutamente seguro de que una variable
tiene un valor específico. Si la condición lógica después de ASSERT resulta ser falsa, se lanza una
excepción no ASSERTION_FAILED ( ASSERTION_FAILED ).
ASSERT 1 = 2. "ERROR
https://riptutorial.com/es/home 17
COND / INTERRUPTOR
SWITCH y COND ofrecen una forma especial de flujo de programa condicional. A diferencia de IF y
CASE , representan valores diferentes basados en una expresión en lugar de ejecutar sentencias.
Es por eso que cuentan como funcionales.
COND
Cuando se deben considerar múltiples condiciones, COND puede hacer el trabajo. La sintaxis es
bastante simple:
COND <type>(
WHEN <condition> THEN <value>
...
[ ELSE <default> | throw <exception> ]
).
Ejemplos
CAMBIAR
SWITCH es una buena herramienta para mapear valores, ya que solo verifica la igualdad, por lo que
es más corto que COND en algunos casos. Si se dio una entrada inesperada, también es posible
lanzar una excepción. La sintaxis es un poco diferente:
SWITCH <type>(
<variable>
WHEN <value> THEN <new_value>
...
[ ELSE <default> | throw <exception> ]
).
Ejemplos
https://riptutorial.com/es/home 18
DATA(lw_language) = SWITCH string(
sy-langu
WHEN 'E' THEN 'English'
WHEN 'D' THEN 'German'
" ...
ELSE THROW cx_sy_conversion_unknown_langu( )
).
https://riptutorial.com/es/home 19
Capítulo 10: Examen de la unidad
Examples
Estructura de una clase de prueba
Las clases de prueba se crean como clases locales en una prueba de unidad especial incluida.
PRIVATE SECTION.
DATA:
mo_cut TYPE REF TO zcl_dummy.
METHODS:
setup,
METHOD dummy_test.
cl_aunit_assert=>fail( ).
ENDMETHOD.
ENDCLASS.
Cualquier método declarado con FOR TESTING será una prueba unitaria. setup es un método
especial que se ejecuta antes de cada prueba.
Un principio importante para las pruebas unitarias es separar el acceso a los datos de la lógica
empresarial. Una técnica eficiente para esto es definir interfaces para el acceso a datos. Su clase
principal siempre usa una referencia a esa interfaz en lugar de leer directamente o escribir datos.
En el código de producción, la clase principal recibirá un objeto que ajusta el acceso a los datos
reales. Esto podría ser una declaración de selección, función llamadas mudule, cualquier cosa
realmente. La parte importante es que esta clase no debe realizar nada más. Ilógico.
Al probar la clase principal, le asigna un objeto que sirve datos estáticos y falsos.
https://riptutorial.com/es/home 20
Un ejemplo para acceder a la tabla SCARR
INTERFACE zif_db_scarr
PUBLIC.
METHODS get_all
RETURNING
VALUE(rt_scarr) TYPE scarr_tab .
ENDINTERFACE.
PRIVATE SECTION.
DATA:
mo_cut TYPE REF TO zcl_main_class.
METHODS:
setup.
ENDCLASS.
https://riptutorial.com/es/home 21
Capítulo 11: Expresiones regulares
Examples
Reemplazo
buscando
Para operaciones de CL_ABAP_REGEX regulares más avanzadas, es mejor usar CL_ABAP_REGEX y sus
clases relacionadas.
Las matches función de predicado se pueden usar para evaluar cadenas sobre la marcha sin
utilizar declaraciones de objetos.
https://riptutorial.com/es/home 22
regex = '[0-9a-f]*' ).
cl_demo_output=>display( 'This will not display' ).
ELSEIF matches( val = '6c6f7665'
regex = '[0-9a-f]*' ).
cl_demo_output=>display( 'This will display' ).
ENDIF.
Al utilizar el método GET_SUBMATCH de la clase CL_ABAP_MATCHER , podemos obtener los datos en los
grupos / subgrupos.
ref_regex->create_matcher(
EXPORTING
text = lv_test
RECEIVING
matcher = ref_matcher
).
ref_matcher->get_submatch(
EXPORTING
index = 0
RECEIVING
submatch = lv_smatch.
https://riptutorial.com/es/home 23
Capítulo 12: Instrumentos de cuerda
Examples
Literales
ABAP ofrece tres operadores diferentes para declarar variables de tipo cadena o de tipo char
Tenga en cuenta que el rango de longitud solo se aplica a los valores codificados. Las variables
internas de CString tienen una longitud arbitraria, mientras que las variables de tipo C siempre
tienen una longitud fija.
Plantillas de cadena
Las plantillas de cadenas son una forma conveniente de mezclar cadenas literales con valores de
variables:
También puede formatear cosas como fechas. Para utilizar el formato de fecha de inicio de sesión
del usuario:
WRITE |The order was completed on { lv_date DATE = USER } and can not be changed|.
Cuerdas de concatenacion
https://riptutorial.com/es/home 24
Las variables de cadena y de tipo char se pueden concatenar usando el comando ABAP
CONCATENATE . Se requiere una variable extra para almacenar los resultados.
Ejemplo:
Taquigrafía
Las versiones más recientes de ABAP ofrecen una variante muy corta de concatenación
utilizando && (operador de encadenamiento).
https://riptutorial.com/es/home 25
Capítulo 13: Objetos ABAP
Examples
Declaración de clase
Constructor, metodos
Implementación de la clase:
METHOD method1.
"Logic
ENDMETHOD.
METHOD method2.
"Logic
method3( ).
ENDMETHOD.
METHOD method3.
"Logic
https://riptutorial.com/es/home 26
ENDMETHOD.
ENDCLASS.
Implementación de la clase:
method1 (
EXPORTING iv_string = lv_string
IMPORTING ev_string = lv_string2
CHANGING cv_string = lv_string3
).
Implementación de la clase:
lv_string = method1( ).
https://riptutorial.com/es/home 27
Tenga en cuenta que los parámetros declarados con RETURNING se pasan solo por valor.
Herencia - definición
Información
La herencia le permite derivar una nueva clase de una clase existente. Haces esto
usando la suma de HERENCIA DE
Implementación de clase
Información
Las adiciones ABSTRACT y FINAL a los métodos y las declaraciones de CLASS le
permiten definir métodos o clases abstractos y finales.
Un método final no se puede redefinir en una subclase. Las clases finales no pueden
tener subclases. Concluyen un árbol de herencia.
Implementación de la clase:
https://riptutorial.com/es/home 28
CLASS lcl_abstract DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS: abstract_method ABSTRACT,
final_method FINAL
normal_method.
ENDCLASS.
METHOD normal_method.
"Some logic
ENDMETHOD.
ENDCLASS.
METHOD abap_class_method.
"Logic
ENDMETHOD.
ENDCLASS.
lo_class->abstract_method( ).
lo_class->normal_method( ).
lo_class->abap_class_method( ).
lo_class->final_method( ).
https://riptutorial.com/es/home 29
Capítulo 14: Plantilla de programas
Sintaxis
• DEFINICIÓN DE CLASE RESUMEN FINAL hace que la clase de programa sea
esencialmente estática, ya que los métodos de instancia nunca podrían usarse. La intención
es mantener la clase mínima.
Examples
Programa OO con métodos de eventos esenciales.
REPORT z_template.
PUBLIC SECTION.
CLASS-METHODS start_of_selection.
CLASS-METHODS initialization.
CLASS-METHODS end_of_selection.
ENDCLASS.
METHOD initialization.
ENDMETHOD.
METHOD start_of_selection.
ENDMETHOD.
METHOD end_of_selection.
ENDMETHOD.
ENDCLASS.
INITIALIZATION.
lcl_program=>initialization( ).
START-OF-SELECTION.
lcl_program=>start_of_selection( ).
END-OF-SELECTION.
lcl_program=>end_of_selection( ).
https://riptutorial.com/es/home 30
programas
https://riptutorial.com/es/home 31
Capítulo 15: Programación dinámica
Examples
Simbolos de campo
Los símbolos de campo son equivalentes de ABAP a los punteros, excepto que los símbolos de
campo siempre están referenciados (no es posible cambiar la dirección real en la memoria).
Declaración
Para declarar un símbolo de campo, debe utilizarse la palabra clave FIELD-SYMBOLS . Los tipos
pueden ser genéricos ( ANY [... TABLE] ) para manejar una amplia variedad de variables.
Asignando
Los símbolos de campo unassigned están unassigned en la declaración, lo que significa que no
apuntan a nada. El acceso a un símbolo de campo sin asignar dará lugar a una excepción y, si no
se detecta, a un volcado breve. Por lo tanto, el estado debe ser verificado con IS ASSIGNED :
IF <fs> IS ASSIGNED.
*... symbol is assigned
ENDIF.
Como solo son referencias, no se pueden almacenar datos reales en el interior. Por lo tanto, los
DATA declarados son necesarios en todos los casos de uso.
Desasignar
A veces puede ser útil restablecer un símbolo de campo. Esto se puede hacer utilizando UNASSIGN .
UNASSIGN <fs>.
* Access on <fs> now leads to an exception again
https://riptutorial.com/es/home 32
Uso para tablas internas
¡Atención! Los símbolos de campo permanecen asignados incluso después de salir del bucle. Si
desea reutilizarlos de forma segura, anule su asignación inmediatamente.
Referencias de datos
Si el tipo de estructura debe decidirse en tiempo de ejecución, podemos definir nuestra estructura
objetivo como referencia a los data tipo genérico.
Para dar un tipo a wa usamos la sentencia CREATE DATA . La adición TYPE se puede especificar por:
Referencia:
Nombre:
https://riptutorial.com/es/home 33
• Debido a que dref sigue siendo una referencia a datos, debe ser anulada ( ->* )
para ser utilizada como contenedor de datos (normalmente se realiza a través de
los símbolos de campo)
Las clases
CL_ABAP_TYPEDESCR
|
|--CL_ABAP_DATADESCR
| |
| |--CL_ABAP_ELEMDESCR
| |--CL_ABAP_REFDESCR
| |--CL_ABAP_COMPLEXDESCR
| |
| |--CL_ABAP_STRUCTDESCR
| |--CL_ABAP_TABLEDESCR
|
|--CL_ABAP_OBJECTDESCR
|
|--CL_ABAP_CLASSDESCR
|--CL_ABAP_INTFDESCR
• DESCRIBE_BY_DATA
• DESCRIBE_BY_NAME
• DESCRIBE_BY_OBJECT_REF
• DESCRIBE_BY_DATA_REF
https://riptutorial.com/es/home 34
Capítulo 16: Tablas internas
Examples
Tipos de tablas internas
Mesa estandar
Esta tabla tiene todas las entradas almacenadas de forma lineal y se accede a los registros de
forma lineal. Para tamaños de mesa grandes, el acceso a la mesa puede ser lento.
Tabla ordenada
Requiere la adición WITH UNIQUE | NON-UNIQUE KEY . La búsqueda es rápida debido a la realización de
una búsqueda binaria. Las entradas no se pueden agregar a esta tabla ya que podría romper el
orden de clasificación, por lo que siempre se insertan con la palabra clave INSERT .
Tabla hash
Requiere la adición WITH UNIQUE | NON-UNIQUE KEY . Utiliza un algoritmo de hashing propietario para
mantener los pares clave-valor. En teoría, las búsquedas pueden ser tan lentas como la tabla
STANDARD , pero en la práctica son más rápidas que una tabla SORTED toma una cantidad de tiempo
constante independientemente del tamaño de la tabla.
https://riptutorial.com/es/home 35
" Declaration of internal table
DATA t_flightb TYPE STANDARD TABLE OF ty_flightb.
Un área de trabajo (comúnmente abreviada wa ) tiene la misma estructura exacta que la tabla,
pero puede contener solo una línea (un WA es una estructura de una tabla con una sola
dimensión).
https://riptutorial.com/es/home 36
Leer, escribir e insertar en tablas internas
" Loop over table i_compc_all and assign current line to structure i_compc_all_line
LOOP AT i_compc_all INTO i_compc_all_line.
CASE i_compc_all_line-ftype. " Read column ftype from current line (which as
assigned into i_compc_all_line)
WHEN 'B'. " Bill-to customer number transformation
i_compc_bil_line = i_compc_all_line. " Copy structure
APPEND i_compc_bil_line TO i_compc_bil. " Append structure to table
" more WHENs ...
ENDCASE.
ENDLOOP.
https://riptutorial.com/es/home 37
Creditos
S.
Capítulos Contributors
No
Empezando con
1 Christian, Community, gkubed, Jagger, mkysoft
ABAP
Clases de mensajes
5 / palabra clave gkubed
MESSAGE
Convenciones de
7 mkysoft
nombres
Declaraciones de
9 Community, gkubed, maillard
flujo de control
Expresiones
11 AKHIL RAJ, gkubed, maillard
regulares
Instrumentos de
12 Achuth hadnoor, Community, maillard, nexxus, Suncatcher
cuerda
Plantilla de
14 nath
programas
Programación
15 Community, gkubed
dinámica
https://riptutorial.com/es/home 38