No te ha pasado que alguna vez has tenido que realizar tareas repetitivas en una PC como copiar de un lado y pegar al otro, una y otra vez? No te parecería excelente poder hacer un programa que hiciera esto por nosotros?
SendInput
SendInput es una función del API de Windows que permite simular ingreso de teclado, movimientos del mouse y hasta click a botones.
Esta función es bastante fácil de utilizar y recibe los siguientes parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
nInputs |
uint |
La cantidad de elementos que se están mandando. |
pInputs |
INPUT * |
Puntero a un arreglo de 1 o más casillas de tipo INPUT que representa el ingreso ya sea de teclado o mouse. |
cbSize |
int |
El tamaño en bytes de cada casilla de tipo INPUT. El valor que se debe mandar es simplemente sizeof(INPUT). |
Como has podido notar, hablo acerca de un tipo de dato INPUT que es una estructura del API de Windows que tiene las siguientes partes:
| Parte | Tipo | Descripción |
|---|---|---|
type |
DWORD |
Identifica el tipo de ingreso, puede ser una de las siguientes constantes:
|
mi |
MOUSEINPUT |
La estructura con la información del movimiento del mouse, solo se considera si el type es INPUT_MOUSE. |
ki |
KEYBDINPUT |
La estructura con la información de la tecla a simular, solo se considera si el type es INPUT_KEYBOARD. |
hi |
HARDWAREINPUT |
La estructura con la información del evento generado por la pieza de hardware , solo se considera si el type es INPUT_HARDWARE. |
En este caso, solo voy a simular ingreso de teclado así que solamente me interesa el type INPUT_KEYBOARD y por lo tanto el valor de ki que es una estructura de tipo KEYBDINPUT que tiene las siguientes partes:
| Parte | Tipo | Descripción |
|---|---|---|
wVk |
WORD |
El código virtual de la tecla. No utilizaremos este atributo. |
wScan |
WORD |
El código de la tecla. Utilizaremos la función MapVirtualKey para asignarle una tecla virtual. |
dwFlags |
DWORD |
Puede ser una combinación de los siguientes valores:
|
time |
DWORD |
La hora en la que se presionó la tecla. La dejaremos en 0 para que se coloque la hora por defecto. |
dwExtraInfo |
ULONG_PTR |
Información adicional que no utilizaremos. |
Enviando teclas a SendInput
Antes de mostrar como se implementa el envío de teclas a través de SendInput es necesario que entiendas cómo se deben enviar las teclas.
Cuando presionamos una tecla de nuestro teclado estamos realizando dos acciones. La primera es presionar la tecla y la segunda es soltar la tecla. La combinación de ambas es necesaria para que el sistema operativo entienda que hemos presionado la tecla.
Al evento de presionar le llamaremos Key Down y al de soltar le llamaremos Key Up. Para diferenciar una tecla en KeyDown y KeyUp tenemos que definir el campo dwFlags de la estructura KEYBDINPUT. Si no hay ningún flag se asume que es el modo KeyDown y si especificamos el flag KEYEVENTF_KEYUP el modo es KeyUp.
Por ejemplo, si queremos presionar la tecla a minúscula, tenemos que mandar dos teclas al SendInput:
- a en modo KeyDown
- a en modo KeyUp
Esta combinación simula que hemos presionado la tecla a.
Si queremos presionar la tecla A mayúscula, tenemos que mandar 4 teclas al SendInput:
- shift en modo KeyDown
- a en modo KeyDown
- a en modo KeyUp
- shift en modo KeyUp
Si te das cuenta, solo hemos mandado la tecla a minúscula pero como hemos mandado la tecla shift en realidad se insertará la tecla A mayúscula. El ejemplo que te acabo de mostrar debería servirte para darte cuenta de que las teclas que tengo que mandar son exactamente iguales a las teclas que presionas cuando escribes. Presionas Shift y lo dejas presionado mientras presionas y sueltas la tecla a y por último sueltas la tecla Shift.
HIBYTE y LOBYTE
Todas las teclas normales tienen posibles valores entre 1-254 por lo que en teoría solo necesitan un byte. Sin embargo, todas las teclas vienen con dos bytes dónde el primero indica si es necesario presionar alguna tecla como Shift, Control, Alt, etc y el segundo byte indica el valor entre 1-254.
Existen dos macros (#define) que permiten separar estos bytes. HIBYTE devuelve el valor del primer byte y LOBYTE el valor del segundo byte.
Utilizaremos estas macros para determinar cuando se tiene que presionar la tecla shift y cuando no. Adicionalmente es importante resaltar que el primer byte no lo debemos mandar a la función SendInput por lo que de existir un primer byte nos aseguraremos de pasar solo el segundo byte.
Usando VkKeyScan para convertir variables de tipo char
Como acabo de explicar, una tecla tiene dos bytes, sin embargo nuestro tipo de dato char está compuesto por un solo byte.
Si queremos mandar variables de tipo char como teclas, es necesario que las convirtamos en su representación en dos bytes. Para esto podemos utilizar la función VkKeyScan que realiza esta conversión devolviendo un dato de tipo SHORT(2 bytes).
Esta conversión es importante debido a que de lo contrario se pueden generar confusiones entre teclas. Por ejemplo el caracter , tiene un solo byte que en hexadecimal se escribe 2C. La tecla VK_SNAPSHOT (imprimir pantalla) tiene dos bytes que en hexadecimal se escriben 00 2C. Como puedes ver, estas teclas son distintas pero en bytes son muy parecidas y son iguales si me quedo con el último byte de cada una. Si son iguales, el sistema no podrá distinguir entre una coma y una impresión de pantalla. Sin embargo si convertimos el caracter , usando VkKeyScan obtendremos un valor de dos bytes que en hexadecimal se escribe 00 BC haciendo posible distinguir entre la coma y la impresión de pantalla.
Caracteres Unicode
Los caracteres unicode como por ejemplo Á é ü son más difíciles de manipular (casi en cualquier lenguaje). El problema con estos caracteres es que no tienen una tecla definida por lo que al hacer VkKeyScan a una de estas teclas se devolverá el valor –1.
Según la documentación de Microsoft el SendInput tiene un flag para caracteres unicode llamado KEYEVENTF_UNICODE pero la verdad es que no lo he podido usar y la documentación está un poco incompleta en este aspecto.
Pero he encontrado una solución al problema. Cuando reciba un caracter unicode lo que voy a hacer es quedarme con su último byte que representa su valor en la tabla ASCII. Usando este valor voy a simular por teclado que estamos presionando Alt Gr + Num. Por ejemplo si llega el caracter @ (no es unicode pero funciona para el ejemplo) cuyo valor en la tabla ASCII es 64 lo que voy a hacer es simular que se está presionando la combinación de teclas Alt Gr + 0064 que si las pruebas en tu teclado muestra el caracter @.
Teclas extendidas
Existe una serie de teclas que se les conoce como extendidas por no estar presentes en todos los teclados. Si queremos mandar alguna de las teclas extendidas es necesario que indiquemos que es extendida utilizando el flag KEYEVENTF_EXTENDEDKEY.
Las teclas extendidas con las que voy a trabajar son: VK_HOME, VK_END, VK_PRIOR, VK_NEXT, VK_RIGHT, VK_LEFT, VK_UP, VK_DOWN, VK_RMENU, VK_INSERT, VK_DELETE, VK_LWIN
Virtual Keys
Las Virtual Keys o teclas virtuales, son constantes definidas en el API de Windows que puedes utilizar para representar a diversas teclas. Por ejemplo VK_SHIFT representa la tecla shift, VK_LMENU representa la tecla Alt izquierda, VK_LWIN representa la tecla de windows izquierda.
Puedes encontrar la lista completa en http://msdn.microsoft.com/en-us/library/aa926323.aspx.
El código fuente
Ahora si, suficiente teoría vamos con la práctica. En este caso NO voy a realizar el código en C# por que este tema es un poco avanzado y C# es un poco limitado (no puedo creer que dije esto pero para algunas cosas es un poco limitado y para otras es muy potente). Bueno, mi opinión sobre C# en este punto no viene al caso, pero si programas en C# anímate a hacerlo en C++ y luego puedes invocar a tu programa terminado desde C# o puedes usar la función SendKey de C#.
Voy a asumir de que si llegaste hasta acá ya sabes crear una clase en C++. Además es necesario que sepas un poco acerca de STL (Standard Template Library) que viene con C++ pero si no lo sabes probablemente puedes entender el código porque solo uso un vector dinámico.
Voy a hacer una clase que permita manejar todo el envío de teclas. La definición de mi clase a la que llamaré CKeyboardSimulator es la siguiente:
#include <windows.h>
#include <vector>
#pragma comment(lib, "user32.lib")
using namespace std;
class CKeyboardSimulator
{
private:
void __appendKeyCode(SHORT vk, bool keyIsUp, vector<INPUT> &vec);
void __SendInput(vector<INPUT> vec);
public:
void SendKeyCodeDown(SHORT vk);
void SendKeyCodeUp(SHORT vk);
void SendKeyCode(SHORT vk);
void SendChar(int c);
void SendString(char *str);
};
Ahora voy a explicar cada uno de los métodos de mi clase a la vez que te muestro la implementación de cada uno de ellos.
Constructor y Destructor
Como puedes ver, mi clase no tiene ni constructor ni destructor. Esto se debe a que no tengo atributos y nada que inicializar, por eso he omitido estos dos métodos.
__appendKeyCode
Esta es la función más complicada de toda la clase. Este método lo utilizo para crear las estructuras de tipo INPUT que tienen que ser enviadas a SendInput.
El primer parámetro representa la tecla a ingresar. El segundo parámetro indica si la tecla que se debe ingresar está en modo KeyDown o KeyUp. El tercer parámetro es un arreglo dinámico STL dónde se almacenará la estructura INPUT generada. Almaceno el resultado en un vector de tal forma que puedo usar esta función varias veces para generar una serie de teclas y luego mandarlas todas al SendInput.
Por ejemplo, si tenemos que mandar la tecla A mayúscula puedo llamar cuatro veces a esta función con los parámetros:
__appendKeyCode(VK_SHIFT, false, vec); // shift en modo KeyDown__appendKeyCode(VkKeyScan('a'), false, vec); // a en modo KeyDown__appendKeyCode(VkKeyScan('a'), true, vec); // a en modo KeyUp__appendKeyCode(VK_SHIFT, true, vec); // shift en modo KeyUp
Así al final tener dentro del vector vec todas las teclas que tengo que mandar al SendInput.
Te dejo el código de la función:
void CKeyboardSimulator::__appendKeyCode(SHORT vk, bool keyIsUp, vector<INPUT> &vec)
{
// Coloco los valores iniciales a la estructura INPUT
INPUT inp;
inp.type = INPUT_KEYBOARD;
inp.ki.time = 0;
inp.ki.dwFlags = KEYEVENTF_SCANCODE;
// Verifico si es una tecla Extendida y agrego el flag correspondiente
if (vk == VK_HOME || vk == VK_END || vk == VK_PRIOR || vk == VK_NEXT ||
vk == VK_RIGHT || vk == VK_LEFT || vk == VK_UP || vk == VK_DOWN ||
vk == VK_RMENU || vk == VK_INSERT || vk == VK_DELETE || vk == VK_LWIN)
{
inp.ki.dwFlags = inp.ki.dwFlags | KEYEVENTF_EXTENDEDKEY;
}
// Verifico si debo agregar KeyDown o KeyUp y agrego el flag correspondiente
if (keyIsUp)
{
inp.ki.dwFlags = inp.ki.dwFlags | KEYEVENTF_KEYUP;
}
// Si necesita Shift, Alt, etc, le quito el primer byte de lo contrario
// lo mando tal cual
if (HIBYTE(vk) > 0)
inp.ki.wScan = MapVirtualKey(LOBYTE(vk), 0);
else
inp.ki.wScan = MapVirtualKey(vk, 0);
// Agrego la tecla al vector dinamico
vec.push_back(inp);
}
Si no entiendes algo de esta función probablemente no has entendido algo en la parte de teoría así que te recomiendo leerla otra vez.
__SendInput
Esta es la función que llama al API SendInput. Recibe como parámetro un vector dinámico con todas las teclas a mandar.
En esta función creamos un arreglo y le asignamos todas las teclas. Por último llamamos al API SendInput y le mandamos el arreglo completo.
void CKeyboardSimulator::__SendInput(vector<INPUT> vec)
{
INPUT *inp = new INPUT[vec.size()];
for (int i = 0; i < vec.size(); i++) {
inp[i] = vec[i];
}
SendInput(vec.size(), inp, sizeof(INPUT));
delete[]inp;
}
SendKeyCodeDown
Esta función envía la tecla en modo KeyDown. Esta se debe complementar con la siguiente función SendKeyCodeUp para que la tecla sea soltada. En pocas palabras, si solo llamas al método SendKeyDown el sistema operativo considerará que todavía sigues presionando la tecla.
void CKeyboardSimulator::SendKeyCodeDown(SHORT vk)
{
vector<INPUT> keys;
if (HIBYTE(vk) > 0) {
__appendKeyCode(VK_SHIFT, false, keys);
__appendKeyCode(vk, false, keys);
}
else {
__appendKeyCode(vk, false, keys);
}
__SendInput(keys);
}
Si la tecla necesita usar SHIFT entonces se mandan dos teclas de lo contrario solo se manda una tecla.
SendKeyCodeUp
Esta función envía la tecla en modo KeyUp. Esta se debe llamar después de la función SendKeyCodeDown para informar que la tecla ha sido liberada.
void CKeyboardSimulator::SendKeyCodeUp(SHORT vk)
{
vector<INPUT> keys;
if (HIBYTE(vk) > 0) {
__appendKeyCode(vk, true, keys);
__appendKeyCode(VK_SHIFT, true, keys);
}
else {
__appendKeyCode(vk, true, keys);
}
__SendInput(keys);
}
Si la tecla necesita usar SHIFT entonces manda dos teclas (SHIFT al último para cerrar el enviado en SendKeyCodeDown) de lo contrario solo se manda una tecla.
SendKeyCode
Esta función manda una tecla completa, es decir, manda tanto el modo KeyDown como el KeyUp, todo en una sola petición. Ten en cuenta que no es lo mismo mandar las teclas por separado (invocando a las dos funciones anteriores) que mandarlas todas juntas.
void CKeyboardSimulator::SendKeyCode(SHORT vk)
{
vector<INPUT> keys;
if (HIBYTE(vk) > 0) {
__appendKeyCode(VK_SHIFT, false, keys);
__appendKeyCode(vk, false, keys);
__appendKeyCode(vk, true, keys);
__appendKeyCode(VK_SHIFT, true, keys);
}
else {
__appendKeyCode(vk, false, keys);
__appendKeyCode(vk, true, keys);
}
__SendInput(keys);
}
Si la tecla necesita SHIFT manda 4 teclas de lo contrario solo manda 2.
SendChar
Esta función permite recibir un caracter (char) y realiza automáticamente la conversión a tecla teniendo en cuenta de que si es un caracter unicode entonces debe mandar una combinación de teclas distintas.
void CKeyboardSimulator::SendChar(int c)
{
SHORT vk = VkKeyScan(c);
if (vk >= 0) {
SendKeyCode(vk);
}
else
{
char buff[5];
sprintf(buff, "%04d", LOBYTE(c));
SendKeyCodeDown(VK_RMENU); // Alt Gr
for (int i=0; i < 4; i++)
{
switch (buff[i])
{
case '0': SendKeyCode( VK_NUMPAD0 ); break;
case '1': SendKeyCode( VK_NUMPAD1 ); break;
case '2': SendKeyCode( VK_NUMPAD2 ); break;
case '3': SendKeyCode( VK_NUMPAD3 ); break;
case '4': SendKeyCode( VK_NUMPAD4 ); break;
case '5': SendKeyCode( VK_NUMPAD5 ); break;
case '6': SendKeyCode( VK_NUMPAD6 ); break;
case '7': SendKeyCode( VK_NUMPAD7 ); break;
case '8': SendKeyCode( VK_NUMPAD8 ); break;
case '9': SendKeyCode( VK_NUMPAD9 ); break;
}
}
SendKeyCodeUp(VK_RMENU);
}
}
SendString
Por último hice esta función para no tener que estar llamando muchas veces a SendChar sino que puedo mandar toda una cadena con una sola invocación.
void CKeyboardSimulator::SendString(char *str)
{
for (int i = 0; str[i] != 0; i++) {
SendChar(str[i]);
}
}
Usando CKeyboardSimulator
Ahora vamos a utilizar nuestra clase CKeyboardSimulator en algunos ejemplos para que tengas una idea de lo que puedes hacer.
Para esto, crea un formulario en entorno visual que tenga 4 botones y un componente TMemo si usas Code Gear o TextBox con opción multiline si usas Visual Studio.
Ahora, agrega la cabecera para incluir a tu clase CKeyboardSimulator.
#include "KeyboardSimulator.h"
Botón Escribir
Implementa el evento click del botón escribir con lo siguiente:
Visual Studio
Code Gear
System::Void btnEscribir_Click(System::Object^ sender, System::EventArgs^ e)
{
CKeyboardSimulator *kb = new CKeyboardSimulator();
textBox1->Focus();
kb->SendString("En Copstone, nos preocupamos\nde que sepas programar");
kb->SendKeyCode(VK_RETURN);
kb->SendKeyCode(VK_RETURN);
kb->SendString("Que te parece esto...");
kb->SendKeyCode(VK_HOME);
kb->SendString("Pregunta: ");
kb->SendKeyCodeDown(VK_SHIFT);
kb->SendKeyCode(VK_END);
kb->SendKeyCodeUp(VK_SHIFT);
delete kb;
}
void __fastcall TForm1::btnEscribirClick(TObject *Sender)
{
CKeyboardSimulator *kb = new CKeyboardSimulator();
Memo1->SetFocus();
kb->SendString("En Copstone, nos preocupamos\nde que sepas programar");
kb->SendKeyCode(VK_RETURN);
kb->SendKeyCode(VK_RETURN);
kb->SendString("Que te parece esto...");
kb->SendKeyCode(VK_HOME);
kb->SendString("Pregunta: ");
kb->SendKeyCodeDown(VK_SHIFT);
kb->SendKeyCode(VK_END);
kb->SendKeyCodeUp(VK_SHIFT);
delete kb;
}
Botón Copiar y Pegar
Implementa el evento click del botón copiar y pegar con lo siguiente:
Visual Studio
Code Gear
System::Void btnCopiarYPegar_Click(System::Object^ sender, System::EventArgs^ e)
{
CKeyboardSimulator *kb = new CKeyboardSimulator();
textBox1->Focus();
kb->SendKeyCodeDown(VK_CONTROL);
kb->SendChar('c');
kb->SendKeyCodeUp(VK_CONTROL);
kb->SendKeyCode(VK_END);
kb->SendKeyCodeDown(VK_CONTROL);
kb->SendChar('v');
kb->SendKeyCodeUp(VK_CONTROL);
delete kb;
}
void __fastcall TForm1::btnCopiarYPegarClick(TObject *Sender)
{
CKeyboardSimulator *kb = new CKeyboardSimulator();
Memo1->SetFocus();
kb->SendKeyCodeDown(VK_CONTROL);
kb->SendChar('c');
kb->SendKeyCodeUp(VK_CONTROL);
kb->SendKeyCode(VK_END);
kb->SendKeyCodeDown(VK_CONTROL);
kb->SendChar('v');
kb->SendKeyCodeUp(VK_CONTROL);
delete kb;
}
Antes de hacer click en este botón asegúrate de que haya texto seleccionado o no habrá nada que copiar y pegar.
Botón Escribir en Notepad
Implementa el evento click del botón escribir en notepad con lo siguiente:
Visual Studio
Code Gear
System::Void btnEnNotepad_Click(System::Object^ sender, System::EventArgs^ e)
{
CKeyboardSimulator *kb = new CKeyboardSimulator();
// Abrir notepad WIN + r
kb->SendKeyCodeDown(VK_LWIN);
kb->SendChar('r');
kb->SendKeyCodeUp(VK_LWIN);
Sleep(1000);
kb->SendString("notepad.exe");
kb->SendKeyCode(VK_RETURN);
Sleep(1000);
kb->SendChar('E'); Sleep(200);
kb->SendChar('n'); Sleep(200);
kb->SendChar(' '); Sleep(200);
kb->SendChar('C'); Sleep(200);
kb->SendString("opstone, nos preocupamos\nde que sepas programar");
kb->SendKeyCode(VK_RETURN);
kb->SendKeyCode(VK_RETURN);
char cad[] = "Que te parece esto...";
for (int i=0; cad[i] != 0; i++)
{
kb->SendChar(cad[i]);
Sleep(200);
}
kb->SendKeyCode(VK_HOME);
char cad2[] = "Pregunta: ";
for (int i=0; cad2[i] != 0; i++)
{
kb->SendChar(cad2[i]);
Sleep(200);
}
delete kb;
}
void __fastcall TForm1::btnEnNotepadClick(TObject *Sender)
{
CKeyboardSimulator *kb = new CKeyboardSimulator();
// Abrir notepad WIN + r
kb->SendKeyCodeDown(VK_LWIN);
kb->SendChar('r');
kb->SendKeyCodeUp(VK_LWIN);
Sleep(1000);
kb->SendString("notepad.exe");
kb->SendKeyCode(VK_RETURN);
Sleep(1000);
kb->SendChar('E'); Sleep(200);
kb->SendChar('n'); Sleep(200);
kb->SendChar(' '); Sleep(200);
kb->SendChar('C'); Sleep(200);
kb->SendString("opstone, nos preocupamos\nde que sepas programar");
kb->SendKeyCode(VK_RETURN);
kb->SendKeyCode(VK_RETURN);
char cad[] = "Que te parece esto...";
for (int i=0; cad[i] != 0; i++)
{
kb->SendChar(cad[i]);
Sleep(200);
}
kb->SendKeyCode(VK_HOME);
char cad2[] = "Pregunta: ";
for (int i=0; cad2[i] != 0; i++)
{
kb->SendChar(cad2[i]);
Sleep(200);
}
delete kb;
}
Botón Efecto Alt + Tab
Implementa el evento click del botón efecto alt + tab con lo siguiente:
Visual Studio
Code Gear
System::Void btnEfectoAltTab_Click(System::Object^ sender, System::EventArgs^ e)
{
CKeyboardSimulator *kb = new CKeyboardSimulator();
kb->SendKeyCodeDown(VK_RMENU);
for (int i=0; i < 10; i++)
{
kb->SendKeyCodeDown(VK_TAB);
Sleep(500);
kb->SendKeyCodeUp(VK_TAB);
}
kb->SendKeyCodeUp(VK_RMENU);
delete kb;
}
void __fastcall TForm1::btnEfectoAltTabClick(TObject *Sender)
{
CKeyboardSimulator *kb = new CKeyboardSimulator();
kb->SendKeyCodeDown(VK_RMENU);
for (int i=0; i < 10; i++)
{
kb->SendKeyCodeDown(VK_TAB);
Sleep(500);
kb->SendKeyCodeUp(VK_TAB);
}
kb->SendKeyCodeUp(VK_RMENU);
delete kb;
}
Si tienes Aero instalado en tu máquina te recomiendo que pruebes el efecto win + tab que se ve bastante mejor.
Visual Studio
Code Gear
System::Void btnEfectoAltTab_Click(System::Object^ sender, System::EventArgs^ e)
{
CKeyboardSimulator *kb = new CKeyboardSimulator();
kb->SendKeyCodeDown(VK_LWIN);
for (int i=0; i < 10; i++)
{
kb->SendKeyCodeDown(VK_TAB);
Sleep(500);
kb->SendKeyCodeUp(VK_TAB);
}
kb->SendKeyCodeUp(VK_LWIN);
delete kb;
}
void __fastcall TForm1::btnEfectoAltTabClick(TObject *Sender)
{
CKeyboardSimulator *kb = new CKeyboardSimulator();
kb->SendKeyCodeDown(VK_LWIN);
for (int i=0; i < 10; i++)
{
kb->SendKeyCodeDown(VK_TAB);
Sleep(500);
kb->SendKeyCodeUp(VK_TAB);
}
kb->SendKeyCodeUp(VK_LWIN);
delete kb;
}
Ideas Adicionales
Algunas ideas de lo que puedes hacer con este conocimiento:
- Realización de macros para la solución de tareas repetitivas.
- Manipulación de PC’s a través de conexión remota.
- Programas de demostración de un software. (Para que el usuario vea como debe hacer las cosas)
Estas son solo algunas de las posibilidades y espero que a tí te sirva para mucho más.

Muy bueno, y habia encontrado algo sobre la estructura INPUT en otras web, pero aca esta detallado, quiere hacer un progrmaacion para administracion remota y empece por esto, pero ahora necesito tutoriales con ejemplos para aprender a usar sockets y ahcer aplicaciones Cliente/Servidor. Ahora me fijo si tienen algun tutorial sobre esto, pero sino por favor posteen alguno. Gracias.
augus1990 AugustoM @u6u$t0