Bienvenidos a un nuevo curso. Ahora estoy en el ciclo de grado superior: Desarrollo de aplicaciones multiplataforma. Y voy a ser parte del experimento de la formación dual. Estaré haciendo practicas durante un año, y a la vez finalizando la formación.

Deseadme suerte.

Mostrando entradas con la etiqueta PRACTICAS. Mostrar todas las entradas
Mostrando entradas con la etiqueta PRACTICAS. Mostrar todas las entradas

viernes, 16 de septiembre de 2016

Override en Prestashop

0

Definición:
Override es el sistema que tiene Prestashop para proteger aquellos archivos considerados modificados: clases y controladores (php) son de los más importantes, ya que, si añades o modificas alguna función en el fichero original y se te ocurre actualizar Prestashop… ¡adiós! Todos los cambios que hayas realizado en ese archivo se borrarán.

Técnica del Override para los archivos .php:
La técnica del override en Prestashop consiste en la creación de un nuevo archivo que lleve dentro una clase que extienda de la original, cuya información sustituirá a la del archivo original, siempre y cuando, el nombre de la función o instrucción coincida con la del archivo original.

Técnica del Override para los módulos:
En la carpeta de tienda/themes/nombre_del_tema/modules/ se guardan una copia del modulo entero o de ciertos archivos para modificarlos desde ahí, en lugar del original tienda/modules/. Si existe una carpeta en dicha dirección, Prestashop lo leerla sobre el original, pero las actualizaciones solo serán en la carpeta original. Si no ves alguna actualización del modulo, borra la carpeta del override ;)

Ejemplo añadir al formulario un ¿como nos conociste?:

TPL
Me dirijo a /themes/nombre_del_tema/authentication.tpl. Ya que los tpl, no tienen clases y funciones, nos toca hacerlo así.Ahí añadimos las veces que corresponda el siguiente texto:

<div form-group>
<label class="control-label col-sm-4">{l s='¿Como nos conoció?'}</label>
<div class="col-sm-6">
<input type="radio" id="conocer" name="conocer" value="amigo"  class="conocer"> {l s = 'A través de un amigo' } <br>
<input type="radio" id="conocer" name="conocer" value="google"  class="conocer"> {l s = 'Buscando en Google' } <br>
<input type="radio" id="conocer" name="conocer" value="blog"  class="conocer"> {l s = 'A través del blog Ofertitas' } <br>
<input type="radio" id="conocer" name="conocer" value="foro"  class="conocer"> {l s = 'A través de un foro' } <br>
<input type="radio" id="conocer" name="conocer" value="twitter"  class="conocer"> {l s = 'A través de las redes sociales' } <br>
<input type="radio" id="conocer" name="conocer" value="otros"  class="conocer"> {l s = 'De otra forma' } <br>
<div class="col-sm-6">
<label class="control-label col-sm-8">{l s='¿De que otra forma?'}</label>
<input type="text" class="is_required validate form-control" data-validate="conocerOtro" id="conocerOtro" name="conocerOtro" value="{if isset($smarty.post.conoceOtro)}{$smarty.post.conoceOtro}{/if}" /><br>
</div>
</div>
</div>

Para tener la última referencia de las plantillas recomiendo un control de versiones.
Y una documentación, comentando las actualizaciones. 

Nos queda así:

El CSS seguiría los mismos pasos que los archivos.tpl

PHP
Ahora llega lo interesante.

Original (controllers/front/AuthController.php)
class AuthControllerCore extends FrontController
{
}

Override (override/controllers/front/AuthController.php)
class AuthController extends AuthControllerCore
{
}

En este archivo es donde guardamos la acción del boton, que nos guardara la información del formulario.
Si buscas submit en el tpl modificado, encontraras que existen 4 botones, del cual, según mi plantilla, necesitare 2 (submitGuestAccount y submitAccount). Investigando el documento vemos la función:

Original (controllers/front/AuthController.php)
class AuthControllerCore extends FrontController
{
          protected function processSubmitAccount()
          {
                    //función original
           }
}

Override (override/controllers/front/AuthController.php)
class AuthController extends AuthControllerCore
{
           protected function processSubmitAccount()
          {
                    //código a añadir:
                  $customer = new Customer();

                  $conocer = Tools::getValue('conocer');
                  $conocerOtro=Tools::getValue('conocerOtro');

                  $customer->referralcode = $conocer;
                  $customer->referralcode = $conocerOtro;

                  $this->context->cookie->customer_referralcode = $customer->$conocer;
                  $this->context->cookie->customer_referralcode = $customer->$conocerOtro;

                   //para que el resto de la función en la original también se ejecute

                    parent::processSubmitAccount();
           }
}

Podrás ver si funciona con la siguiente "marquita"




Ahora definimos los nuevos campos y los añadimos al objeto customer


Original (classes/Customer.php)
class CustomerCore extends ObjectModel
{

}

Override(override/classes/Customer.php)
class Customer extends CustomerCore
{
    //declaración:
    public $conocer;
    public $conocerOtro;

//definición del objeto:
como no es una función que se pueda 'separar' la creamos entera
public static $definition = array(
        'table' => 'customer',
        'primary' => 'id_customer',
        'fields' => array(
            'secure_key' => array('type' => self::TYPE_STRING, 'validate' => 'isMd5', 'copy_post' => false),
            'lastname' => array('type' => self::TYPE_STRING, 'validate' => 'isName', 'required' => true, 'size' => 32),
            'firstname' => array('type' => self::TYPE_STRING, 'validate' => 'isName', 'required' => true, 'size' => 32),
            'email' => array('type' => self::TYPE_STRING, 'validate' => 'isEmail', 'required' => true, 'size' => 128),
            'passwd' => array('type' => self::TYPE_STRING, 'validate' => 'isPasswd', 'required' => true, 'size' => 32),
            'last_passwd_gen' => array('type' => self::TYPE_STRING, 'copy_post' => false),
            'id_gender' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
            'birthday' => array('type' => self::TYPE_DATE, 'validate' => 'isBirthDate'),
            'newsletter' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'newsletter_date_add' => array('type' => self::TYPE_DATE,'copy_post' => false),
            'ip_registration_newsletter' => array('type' => self::TYPE_STRING, 'copy_post' => false),
            'optin' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'website' => array('type' => self::TYPE_STRING, 'validate' => 'isUrl'),
            'company' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName'),
            'siret' => array('type' => self::TYPE_STRING, 'validate' => 'isSiret'),
            'ape' => array('type' => self::TYPE_STRING, 'validate' => 'isApe'),
            'outstanding_allow_amount' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'copy_post' => false),
            'show_public_prices' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool', 'copy_post' => false),
            'id_risk' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'copy_post' => false),
            'max_payment_days' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'copy_post' => false),
            'active' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool', 'copy_post' => false),
            'deleted' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool', 'copy_post' => false),
            'note' => array('type' => self::TYPE_HTML, 'validate' => 'isCleanHtml', 'size' => 65000, 'copy_post' => false),
            'is_guest' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool', 'copy_post' => false),
            'id_shop' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'copy_post' => false),
            'id_shop_group' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'copy_post' => false),
            'id_default_group' => array('type' => self::TYPE_INT, 'copy_post' => false),
            'id_lang' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'copy_post' => false),
            'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate', 'copy_post' => false),
            'date_upd' => array('type' => self::TYPE_DATE, 'validate' => 'isDate', 'copy_post' => false),
            'conocer' =>                    array('type' => self::TYPE_STRING, 'validate' => 'isName', 'size' => 100),
            'conocerOtro' =>                array('type' => self::TYPE_STRING, 'validate' => 'isName', 'size' => 100),
        ),

    );
}


Por último modifico la base de datos:
ALTER TABLE `pp_customer`
ADD `conocer` VARCHAR( 100 );
ALTER TABLE `pp_customer`
ADD `conocerOtro` VARCHAR( 100 );


Para hacer la prueba es recomendable tener activado el aviso de errores.

En config/defines.inc.php
/* Debug only */
if (!defined('_PS_MODE_DEV_'))
define('_PS_MODE_DEV_', true);

Al hacer la prueba veo que se ha enfadado el smarty

Busco en el foro de prestashop, y la solución es borrar cache, obligar a compilar y si sigue fallando quitar el depurador de errores, ya que no es un error grave o que nos pueda afectar. Más info aquí y aquí.

También recomiendo esta léctura sobre ObjectModel, del cual creare una entrada más adelante. Con más ejemplos. Así como subiré como hacer para que se vea la nueva información en el backOffice (administración).

Por último la prueba final:







viernes, 9 de septiembre de 2016

Como saber de donde vienen tus ventas

0

Si has anunciado tus productos en un blog como ofertitas y quieres saber cuantos beneficios te ha aportado el blog, la consulta es la siguiente:

select po.invoice_date as fecha, po.id_order as pedido, pcs.http_referer as 'fuente final',
       pod.product_id as 'id producto' , pod.product_name as 'nombre producto',
pod.product_quantity as cantidad, pod.product_price as precio
from  ppp_connections_source pcs, ppp_connections pc,ppp_guest pg,
      ppp_orders po, ppp_order_detail pod
where pcs.id_connections=pc.id_connections
and pc.id_guest=pg.id_guest
and pg.id_customer=po.id_customer
and po.id_order=pod.id_order
and po.invoice_date between '2016-06-25' and '2016-09-02'
and pc.id_connections in (
select pc.id_connections
from ps_connections pc, ps_connections_source pcs
where pc.id_connections=pcs.id_connections
and pcs.http_referer like '%ofertitas%')
group by 2
order by 1
;

Por cierto,si te fijas no sale siempre la opción de fuentes en pedidos, creo que es debido a como cada uno configura su navegador. Pero veras que analytics tiene más datos de la procedencia.

miércoles, 7 de septiembre de 2016

Aclaraciónes Curl - xml

0

Hay cosas que parecen tontas, pero si no lo sabes, no lo son. 
Así que quiero hacer un par de aclaraciones y enseñaron un par de trucos. 

¿Recordáis la historia? Quiero que los pedidos de licencias se hagan automáticamente, para ello debo identificar que productos son licencias y luego enviar un xml a la empresa con un post. 

//dentro del modulo, tengo un hook de escucha que se activa si se ha realizado un pedido
public function hookNewOrder($params)
{
//El numero de pedido
$id_order = (int)Tools::getValue('id_order');
//Con el numero de pedido puedo ver las referencias de los productos de dicho pedido (esta comprobación esta dentro de una clase)
$referencia = "SELECT p.reference
                   FROM product p, order_detail od
                   WHERE od.id_order=$id_order
                   AND p.id_product=od.product_id;";
//query realiza una consulta a la base de datos
        $ref Db::getInstance()->query($referencia);
//comprobamos las referencias una por una
while ($re = $ref->fetch())
{
//Compruebo que sea un producto de licencia. (esta comprobación esta dentro de una clase)
$licencia = "SELECT P.reference
                FROM product_lang PL, product P
                WHERE P.reference = '$reference'
                AND PL.name like '% manzana %'
                AND PL.id_product=P.id_product";
//getRow ejecuta una consulta y devuelve la primera fila
        $li Db::getInstance()->getRow($licencia);
//=== si son idénticos
if($re["reference"]===$li["reference"]
{
//necesitamos enviar un valor único a la transferencia, sin modificar la base de datos
//getdate obtiene información de la fecha/hora. Si sumas la fecha y hora, tendrás siempre un valor único.
$tiempo = getdate();
                    $t = $tiempo[year]  + $tiempo[mon]  + $tiempo[mday] + $tiempo[hours]  + $tiempo[minutes] + $tiempo[seconds];
//el post debería estar en un php aparte, para aclarar el texto. 
$service_url = 'http://example.com/api/conversations';
/* inicia sesión curl*/
// el distribuidor nos solicita un archivo xml, pero recuerda que no puedes enviar una variable en xml, y no es necesario un archivo.xml. Lo que necesitas hacer es generar el xml con php. Así que aunque lo lógico sea pensar en usar $input_xml, este genera un xml, así que no te leerá la variable. Por lo que se usa el $curl_post_data.
$curl_post_data = "      <Order>
                        <Version>2.5</Version>
                        <Transaction>
                            <TransactionID>$T</TransactionID>
                        </Transaction>
                        <OrderInformation>
                            ...
                        </OrderInformation>
                    </Order>";
// ya os comente la mayoría de las funciones usadas con curl(+)
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_URL, $service_url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_POSTFIELDS, $curl_post_data);
$curl_response = curl_exec($curl);
if ($curl_response === false) {
    $info = curl_error($curl);
    curl_close($curl);
    $resultado= 'error occured during curl exec. Additioanl info: ' . var_export($info);
//print_r te sacara por pantalla el resultado 
    print_r($resultado);
//exit terminara el programa, es importante salir o continuara el programa convirtiendo el resultado en ok y llevando a error. 
    exit();
}
curl_close($curl);
//json_decode — Decodifica un string de JSON(+)
$decoded = json_decode($curl_response);
//var_dump muestra información estructurada sobre una o más expresiones incluyendo su tipo y valor. (+)
var_dump($curl_response);
/* fin sesión curl*/
if (isset($decoded->response->status) && $decoded->response->status == 'ERROR') {
    $resultado= 'error occured: ' . $decoded->response->errormessage;
    print_r($resultado);
    exit();
}
$resultado= 'response ok!';
var_export($decoded->response);
print_r($resultado);
exit();                    
}
}
}

viernes, 19 de agosto de 2016

cURL

0

El otro día subía el código para un post, donde se utilizaba esta biblioteca. Pero me doy cuenta que necesita una mirada más a fondo. Puedes recordarla aquí

Curl es una librería de funciones para conectar con servidores para trabajar con ellos. El trabajo se realiza con formato URL. Es decir, sirve para realizar acciones sobre archivos que hay en URLs de Internet, soportando los protocolos más comunes, como http, ftp, https, etc.

En lo que respecta a PHP, Curl está integrado dentro, de manera que esas librerías también se pueden utilizar desde scripts PHP. 

Las funciones utilizadas en mi anterior código fueron:

curl_init (+)

resource curl_init ([ string $url = NULL ] )

Inicia una nueva sesión y devuelve el manipulador curl para el uso de las funciones curl_setopt(), curl_exec(), y curl_close(). Devuelve un manipulador de cURL si todo fué bien, FALSE si hay errores.

curl_setopt (+)

bool curl_setopt ( resource $ch , int $option , mixed $value )

Establece una opción en la sesión del recurso cURL.

Los parámetros son los siguientes:

ch: El recurso cURL devuelto por curl_init().

option: La opción CURLOPT_XXX a configurar.

value: Valor a configurar en la option.

A continuación, especifico los parámetros que utilice, para estos parámetros en necesario que value sea bool:

CURLOPT_RETURNTRANSFER

TRUE para devolver el resultado de la transferencia como string del valor de curl_exec() en lugar de mostrarlo directamente.

CURLOPT_POST

TRUE para hacer un HTTP POST normal. Este POST del tipo application/x-www-form-urlencoded, el más común en formularios HTML.

para este parámetro en necesario que value sea string:

CURLOPT_POSTFIELDS

Todos los datos para enviar vía HTTP "POST". para enviar un fichero, prefija el nombre del fichero con @ y utiliza la ruta completa. Se puede especificar explícitamente el tipo de fichero añadiendo el tipo al nombre del fichero, en el formato ';type=mimetype'. Se puede enviar una cadena urlencoded como 'para1=val1&para2=val2&...' o también como un array con el nombre del campo como key y los datos como value. Si value es un array, el encabezado Content-Type será definido como multipart/form-data. .

curl_exec (+)

mixed curl_exec ( resource $ch )

Ejecuta la sesión cURL que se le pasa como parámetro.

Esta función debe llamarse después de inicializar una sesión cURL y todas las opciones para la sesión están establecidas.

El parámetro es el siguiente:

ch: El recurso cURL devuelto por curl_init().

Devuelve TRUE en caso de éxito o FALSE en caso de error. Sin embargo, si la opción CURLOPT_RETURNTRANSFER está establecida, devolverá el resultado si se realizó con éxito, o FALSE si falló.

curl_getinfo (+)

mixed curl_getinfo ( resource $ch [, int $opt = 0 ] )

Obtiene información sobre la última transferencia.

Los parámetros son los siguientes:

ch: El recurso cURL devuelto por curl_init().

opt: Opciones infinitas.

curl_close (+)

mixed curl_exec ( resource $ch )

Ejecuta la sesión cURL que se le pasa como parámetro.

Esta función debe llamarse después de inicializar una sesión cURL y todas las opciones para la sesión están establecidas.

El parámetro es el siguiente:

ch: El recurso cURL devuelto por curl_init().

Devuelve TRUE en caso de éxito o FALSE en caso de error. Sin embargo, si la opción CURLOPT_RETURNTRANSFER está establecida, devolverá el resultado si se realizó con éxito, o FALSE si falló.

jueves, 18 de agosto de 2016

Si no se te ven algunas imagenes

0

La primera causa puede ser que se haya modificado algo del .htaccess, para solucionarlo debe ir a preferencias > seo&urls, bajas un poco y pulsas directamente en guardare (URL amigable sí). Esta acción regenera el .htoccess



Si eso no lo soluciona puedes probar a regenerar las imágenes en miniatura. 
En preferencias>imágenes. Opción regenerar imágenes en miniatura. 


Añadir grupo de clientes a Categorías

0

Si creas un nuevo grupo de clientes en Prestashop, por defecto no tendrá ninguna categoría asociada. Es decir, este nuevo grupo no podrá ver/acceder ninguna de las categorías de productos de la tienda on-line. Para hacer que el nuevo grupo pueda acceder/visualizar las categorías, es necesario editar cada una de ellas, otorgando permiso a este nuevo grupo.

Las puedes editar desde Catalogo>Categorías>Modificar clicando sobre el grupo a añadir o quitar.



Si quieres añadir el nuevo grupo de clientes de Prestashop a todas las categorías y tienes bastantes, pouedes ejecutar el siguiente script en nuestro mysql. 

Ejemplo:

INSERT IGNORE INTO `ps_category_group` (`id_category`,`id_group`)
SELECT DISTINCT `id_category`, 7 /*este numero es el ID del nuevo grupo creado*/ 
FROM `ps_category`

Más info aquí

miércoles, 17 de agosto de 2016

media queries responsive css

0

Media query es una tecnología css introducida en css3 que, utiliza la regla @media para introducir un bloque de propiedades de css sólo si una determinada condición es verdadera.

Podemos añadir un punto de interrupción en ciertas partes del diseño se comportan de manera diferente en cada lado del punto de interrupción.

Se recomienda utilizar un punto de interrupción con el primer móvil, eso hará que la visualización de la página más rápido en los dispositivos más pequeños

El primer móvil significa diseñar para móviles antes de diseñar para el escritorio o cualquier otro dispositivo. En lugar de cambiar estilos cuando el ancho de 768px se hace más pequeño, hay que cambiar el diseño cuando el ancho de 768px se hace más grande.

Puede añadir tantos puntos de ruptura como desee. Como insertar un punto de ruptura entre las tabletas y los teléfonos móviles mediante la adición de una mayor consulta de medios de 600px, y un conjunto de nuevas clases de dispositivos de más de 600px, pero más pequeño que 768px.

Las consultas de medios también se pueden utilizar para cambiar el diseño de una página dependiendo de la orientación del navegador. Puede tener un conjunto de propiedades de CSS que sólo se aplica cuando la ventana del navegador es más ancho que su altura, lo que se denomina orientación "horizontal".

Un ejemplo:

css


/* For mobile phones: */
[class*="col-"] {
width: 100%;
}
@media only screen and (min-width: 600px) and (max-width: 768px) {
/* For tablets: */
.col-m-1 {width: 8.33%;}
.col-m-2 {width: 16.66%;}
.col-m-3 {width: 25%;}
.col-m-4 {width: 33.33%;}
.col-m-5 {width: 41.66%;}
.col-m-6 {width: 50%;}
.col-m-7 {width: 58.33%;}
.col-m-8 {width: 66.66%;}
.col-m-9 {width: 75%;}
.col-m-10 {width: 83.33%;}
.col-m-11 {width: 91.66%;}
.col-m-12 {width: 100%;}
}
@media only screen and (min-width: 768px) {
/* For desktop: */
.col-1 {width: 8.33%;}
.col-2 {width: 16.66%;}
.col-3 {width: 25%;}
.col-4 {width: 33.33%;}
.col-5 {width: 41.66%;}
.col-6 {width: 50%;}
.col-7 {width: 58.33%;}
.col-8 {width: 66.66%;}
.col-9 {width: 75%;}
.col-10 {width: 83.33%;}
.col-11 {width: 91.66%;}
.col-12 {width: 100%;}
}


html


<div class="row">
<div class="col-3 col-m-3">...</div>
<div class="col-6 col-m-9">...</div>
<div class="col-3 col-m-12">...</div>
</div>


Cuando se usa para trabajar con el tamaño de las imagenes, se recomienda el uso del ancho máximo al 100%, esto permite que la foto se haga más pequeña, pero no más grande que su tamaño original. 

ejemplo css:


img {
width: 100%;
height: auto;
}


Para más información aquí

Etiquetas actuales

BD (67) DEF (64) PROG (64) SQL (44) Java (29) PRACTICAS (20) php (18) DI (16) PRESTASHOP (16) PROGRAMACIÓN WEB (16) HTML (13) SGE (12) ERP (9) CONSULTAS (8) css (8) Linux (5) XML (5) Android (4) PDM (4) C (3) NetBeans (3) PSP (3) SMARTY (3) comandos (3) HOOK (2) POST (2) XSD (2) cURL (2) JS (1) MEDIA-QUERYS (1) PDO (1) RESPONSIVE (1) TPL (1) TRADUCCIÓN (1) app_inventor (1)

Todas las etiquetas

EJER (78) BD (67) DEF (64) PROG (64) SQL (44) c# (40) Programación (39) Ficheros (36) Java (29) bases de datos (21) PRACTICAS (20) lenguajes de marcas (19) AD (18) Entorno de desarrollo (18) php (18) PROCEDIMIENTOS (17) DI (16) FORM (16) PRESTASHOP (16) PROGRAMACIÓN WEB (16) lenguaje C (16) E/R (14) HTML (13) SGE (12) Sistemas informáticos (10) ERP (9) CONSULTAS (8) TRANSACCIONES (8) TRIGGER (8) VISUAL BASIC (8) css (8) FUNCIONES (7) html5 (6) Ada (5) EXAMEN (5) Linux (5) XML (5) estructuras (5) Android (4) DISEÑO (4) INTERFAZ (4) LOG (4) OpenBravo (4) PDM (4) ACTUALIZAR (3) C (3) DIAGRAMA (3) Directorios (3) NEW (3) NOR (3) NetBeans (3) OLD (3) PSP (3) SMARTY (3) comandos (3) css3 (3) AISLAMIENTOS (2) C++ (2) CONTROLERRORES (2) ELIMINAR (2) HOOK (2) INSERTAR (2) INST (2) MULTITABLA (2) POST (2) RECURSIVIDAD (2) SUBCONSULTAS (2) VISTAS (2) XSD (2) cURL (2) punteros (2) AJENA (1) BLOQUEOS (1) Byte (1) CREACION (1) CRM (1) Configuración (1) Controles (1) Datos (1) GOTFOCUS (1) IMAGENES (1) INDICES (1) JS (1) Lenght (1) MEDIA-QUERYS (1) Mingw (1) MonoDeveloped (1) OPTIMISTA (1) PDO (1) PESIMISTA (1) RESPONSIVE (1) SPEAK (1) Scanner (1) Serializacion (1) Streams (1) System (1) TPL (1) TRADUCCIÓN (1) USUARIOS (1) UseSystemPasswordChar (1) app_inventor (1) char (1) examenes (1) libreoffice (1) make (1) redes (1)