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

viernes, 17 de mayo de 2013

CodeIgniter 2: Implementar mecanismo de idiomas para tus aplicaciones PHP

CodeIgniter 2 trae su propio sistema de regionalización de idiomas, pero francamente me parece poco práctico, pues se basa en crear arreglos asociativos con las diferentes cadenas de traducción, el problema de esta técnica es que se requiere consultar constantemente el archivo de configuración para ver el nombre de la llave del arreglo y poder obtener la cadena, en cambio, sugiero usar un sistema más parecido al que usa Magento, que traduce la cadena en función de la cadena misma, y utiliza un sencillo sistema de archivos separados por comas y en pares.

Así que los pasos para implementar un sistema de regionalización de idiomas son:

Paso 1.

Crear en la carpeta application/language/, los diferentes códigos de idiomas, en mi caso, utilzaré en_US/ y es_MX/

Paso 2.

Nuestra base siempre va a ser el inglés de estados unidos, así que vamos a crear en la carpeta en_US un archivo llamado core_general.csv

Paso 3.

Es importante señalar que uno puede organizar los archivos de idiomas según su ámbito, por ejemplo, podemos crear uno llamado core_exceptions.cvs, otro para core_database.csv, otro para sales.csv, otro para checkout.csv, otro crm.csv, etc.

Paso 4.

La técnica es usar los archivos separados por comas en pares: tal como se muestra el ejemplo, recordemos que la base es el idioma inglés de E.U., en este ejemplo te muestro los dos archivos, el que colocaremos en la carpeta en_US/ y el que colocaremos en la carpeta es_MX/, con esto ya se va comprendiendo más la lógica:

en_US/core_general.csv

"ERP","ERP"
"MRP","MRP"
"CRM","CRM"
"FRM","FRM"
"HRM","HRM"
"SCM","SCM"
"Enterprise Resource Planning","Enterprise Resource Planning"
"Material Resource Planning","Material Resource Planning"
"Customer Relationship Management","Customer Relationship Management"
"Financial Resource Management","Financial Resource Management"
"Human Resource Management","Human Resource Management"
"Supply Chain Management","Supply Chain Management"
"Inbox","Inbox"
"Personal","Personal"
"Important","Important"
"Pending","Pending"
"Archived","Archived"
"archived","archived"
"Schedule","Schedule"
"Calendar","Calendar"
"Notes","Notes"
"Groups","Groups"
"Messages","Messages"
"Create/Join Groups","Create/Join Groups"
"Go!","Go!"
"Search...","Search..."
"Edit Profile","Edit Profile"
"View Profile","View Profile"
"View All","View All"
"Logout","Logout"
"Login","Login"
"",""
"",""
"",""
"",""
"",""
"",""

es_MX/core_general.csv

"ERP","PRE"
"MRP","PRM"
"CRM","GAC"
"FRM","GRF"
"HRM","GRH"
"SCM","GCS"
"Enterprise Resource Planning","Planificación de recursos empresariales"
"Material Resource Planning","Planificación de recursos materiales"
"Customer Relationship Management","Gestión de atención al cliente"
"Financial Resource Management","Gestión de recursos financieros"
"Human Resource Management","Gestión de recursos humanos"
"Supply Chain Management","Gestión de la cadena de suministro"
"Inbox","Bandeja de entrada"
"Personal","Personal"
"Important","Importante"
"Pending","Pendiente"
"Archived","Archivados"
"archived","archivados"
"Schedule","Agenda"
"Calendar","Calendario"
"Notes","Notas"
"Groups","Grupos"
"Messages","Mensajes"
"Create/Join Groups","Crear/Unir grupos"
"Go!","Ir..."
"Search...","Buscar..."
"Edit Profile","Modificar perfil"
"View Profile","Ver perfil"
"View All","Ver todo"
"Logout","Desconectar"
"Login","Conectar"
"",""
"",""
"",""
"",""
"",""
"",""

Paso 5.

Ahora, vamos a crear un helper, llamado translate_helper.php y lo vamos a colocar en la carpeta application/helpers/

<?php

/**
 * Regresa una cadena con la traducción especificada.
 * Si se especifica $code, se regresa la cadena en ese idioma, 
 * si no se envía el parámetro opcional $code, entonces la 
 * cadena es regresada en el idioma especificado en el archivo 
 * config.php de CI.
 * 
 * @param string $texto La cadena a traducir
 * @param string $archivo El archivo de traducción, por default, el core_general.csv
 * @param string $codigo El código idioma_región, pe: es_MX
 * @return string
 */
function __($texto, $archivo = 'core_general', $codigo = NULL)
{
    $texto_str = (string) $texto;

    # validar si la cadena viene vacía
    if ('' == $texto_str) {
        return '';
    }

    # quitar espacios a la cadena
    $texto_ok = trim($texto);

    # inicializar locale por si no se encuentra la cadena a traducir
    $text_locale = $texto_ok;

    # validar código de idiomas
    if (!is_string($codigo)) {
        $codigo = config_item('locale_default');
    } else {
        $valid_code = array_search($codigo, config_item('locale_list'));

        # si el código locale no está en la lista de paquetes de idioma disponibles,
        # entonces, tomar el default.
        if (FALSE === $valid_code) {
            $codigo = config_item('locale_default');
        }
    }

    $archivo = PATH_RAIZ . URL_ROOT_FOLDER . APPPATH . DIR_LANG . $codigo . '/' . $archivo . '.csv';

    try {
        $handle = @fopen($archivo, 'rb');
    } catch (Exception $e) {
        return $text_locale;
    }
    
    if (FALSE !== $handle) {
        while (FALSE !== ($data = fgetcsv($handle, 1000, ','))) {
            if ($texto_ok == $data[0]) {
                $text_locale = $data[1];
                break;
            }
        }
        fclose($handle);
    }

    return $text_locale;
}

/*  EOF  */

Paso 6.

Ahora necesitamos crear unas constantes, así que editamos el archivo application/config/constants.php y declaramos las siguientes constantes:

define('PATH_RAIZ', $_SERVER['DOCUMENT_ROOT'] . '/');
define('URL_ROOT_FOLDER', ''); // si tu aplicación corre desde un(os) subdirectorio(s), ponerlo aquí por ejemplo: define('URL_ROOT_FOLDER', 'misubfolder/');
define('DIR_LANG', 'language/');

Paso 7.

Editamos el archivo application/config/autoload.php y agregamos el nuevo helper (translate) a la lista, en mi ejemplo se vería así:

$autoload['helper'] = array('url', 'form', 'directory', 'file', 'html', 'date', 'cookie', 'translate');

Paso 8.

En el mismo archivo autoload.php debemos colocar en una sección llamada "Auto-load config files", lo siguiente:

/*
| -------------------------------------------------------------------
|  Auto-load Config files
| -------------------------------------------------------------------
| Prototype:
|
| $autoload['config'] = array('config1', 'config2');
|
| NOTE: This item is intended for use ONLY if you have created custom
| config files.  Otherwise, leave it blank.
|
*/

$autoload['config'] = array('locale');

Paso 9.

Ahora debemos crear el archivo de configuración locale.php en la carpeta application/config/ y colocamos el siguiente contenido:

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

$config['locale_default'] = 'es_MX';

//$config['locale_list'] = array('en_US', 'en_GB', 'es_ES', 'es_AR', 'es_CL', 'es_MX', 'es_UY', 'es_VE', 'fr_FR', 'pt_BR');

# idiomas disponibles, en mi caso, solamente manejaré éstos dos:
$config['locale_list'] = array('en_US', 'es_MX');

/* EOF */

Paso 10.

Listo, ahora ya estamos preparados para usar nuestro archivo de traducciones en cualquier tipo de script: vista, controlador, modelo, helpers, librerías, etc., aquí un ejemplo para usarlo en una vista:

<ul>
    <li class="admin-username"><?php echo $username; ?></li>
    <li><a href="#"><?php echo __('Edit Profile'); ?></a></li>
    <li><a href="#"><?php echo __('View Profile'); ?></a><a href="#"><i class="icon-lock"></i><?php echo __('Logout'); ?

></a></li>
    <li class="active"><a href="#messages" class="icon-inbox" title="<?php echo __('Messages'); ?>"></a></li>
    <li><a href="#schedule" class="icon-calendar" title="<?php echo __('Schedule'); ?>"></a></li>
    <li><a href="#groups" class="icon-group" title="<?php echo __('Groups'); ?>"></a></li>
</ul>

11. Por último, las variantes para este helper, serían por ejemplo:

__('String to translate', 'sales'); // usar el archivo sales.csv
__('String to translate', 'sales', 'fr_FR'); // usar el archivo sales.csv y además forzar a usar el idioma francés de Francia.

Espero que le sea de utilidad.

jueves, 16 de mayo de 2013

Convertir números a letras usando PHP 5 / Poo y aplicando MVC

Es la misma función que hice para convertir números a letras (tanto scriptera como la versión para CodeIgniter), pero ahora hecha con programación orientada a objetos

Como un valor adicional, vamos a aplicar el patrón de diseño MVC (modelo, vista, controlador)

Para este ejemplo voy a suponer que ya conoces las bases de POO (programación orientada a objetos), de lo contrario sugiero primero buscar documentación en la web

Puedes ejecutar el demo desde aquí Convertir números a letras PHP 5 Poo/MVC

El código fuente lo puedes descargar desde aquí poo_numtoletras.zip

Primero, debemos crear una estructura de archivos que contenga:

  • un archivo index.php
  • una carpeta controller
    • un archivo c_numeros.php
  • una carpeta model
    • un archivo m_numeros.php
  • una carpeta view
    • un archivo v_numeros.php

Segundo, editamos el archivo ruteador index.php y colocamos este código:

<?php

/**
 * Ruteador para aplicar el modelo MVC simple
 * 
 * @author Ultiminio Ramos Galán <contacto@ultiminioramos.com>
 * @date 2013-05-16
 */

# cargar el controlador
include 'controller/c_numeros.php';

$instancia = new C_numeros();

$instancia->index();

/* EOF */

Tercero, editamos el archivo del controlador c_numeros.php y colocamos este código:

<?php
/**
 * Clase controladora
 * 
 * @author Ultiminio Ramos Galán <contacto@ultiminioramos.com>
 * @date 2013-05-16
 */

class C_numeros
{

    protected $_letras = '';
    protected $_cantidad;

    public function __construct()
    {
        
    }

    public function index()
    {
        if (isset($_POST['cantidad'])) {
            # preparar la variable
            $this->_prepararDatos();

            # cargar el modelo
            $modeloNum = $this->_cargarModelo('m_numeros');

            # ejecutar la conversion a letras
            try {
                $this->_letras = $modeloNum->numeroLetras($this->_cantidad);
            } catch (Exception $e) {
                $letras = 'Ocurrió un error.';
            }
        }

        # cargar la vista
        $view_data['cantidad'] = $this->_cantidad;
        $view_data['letras'] = $this->_letras;

        $this->_cargarVista('v_numeros', $view_data);
    }

    /**
     * En este método puedes validar el dato de entrada y 
     * agregar funcionalidad adicional
     * 
     * @return void
     */
    protected function _prepararDatos()
    {
        $this->_cantidad = trim($_POST['cantidad']);

        return;
    }

    /**
     * Instancia la clase del modelo y regresa el objeto
     * 
     * @param string $modelo
     * @return object
     */
    protected function _cargarModelo($modelo)
    {
        include "model/{$modelo}.php";
        
        return new M_numeros();
    }

    /**
     * Cargar la vista
     * 
     * @param string $vista
     * @param array $datos_vista
     */
    protected function _cargarVista($vista, $datos_vista)
    {
        include "view/{$vista}.php";
    }

}

Cuarto, editamos el archivo de la vista v_numeros.php y colocamos este código:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Convertir números a letras con PHP 5/Poo </title>
    </head>
    <body>
        <form name="forma1" action="" method="post">
            <div style="width: 100%; display: block;">
                <input id="cantidad" type="text" name="cantidad" value="<?php echo $datos_vista['cantidad']; ?>" size="50" maxlength="21" /> 
                <input id="boton1" type="submit" name="boton1" value="Convertir..." />
            </div>
            <div style="width: 100%; display: block;">
                <textarea id="cantidad_letras" cols="100" rows="5"><?php echo $datos_vista['letras']; ?></textarea>
            </div>
        </form>

        <script type="text/javascript" charset="utf-8">
        </script>
    </body>
</html>

Quinto, editamos el archivo del modelo m_numeros.php y colocamos este código:

<?php
/**
 * Clase del proceso de negocio (modelo)
 * 
 * @author Ultiminio Ramos Galán <contacto@ultiminioramos.com>
 * @date 2013-05-16
 */

class M_numeros
{

    /**
     * Convierte un número en una cadena de letras, para el idioma
     * castellano, pero puede funcionar para español de mexico, de  
     * españa, colombia, argentina, etc.
     * 
     * Máxima cifra soportada: 18 dígitos con 2 decimales
     * 999,999,999,999,999,999.99
     * NOVECIENTOS NOVENTA Y NUEVE MIL NOVECIENTOS NOVENTA Y NUEVE BILLONES
     * NOVECIENTOS NOVENTA Y NUEVE MIL NOVECIENTOS NOVENTA Y NUEVE MILLONES
     * NOVECIENTOS NOVENTA Y NUEVE MIL NOVECIENTOS NOVENTA Y NUEVE PESOS 99/100 M.N.
     * 
     * @author Ultiminio Ramos Galán <contacto@ultiminioramos.com>
     * @param string $numero La cantidad numérica a convertir 
     * @param string $moneda La moneda local de tu país
     * @param string $subfijo Una cadena adicional para el subfijo
     * 
     * @return string La cantidad convertida a letras
     */
    public function numeroLetras($numero, $moneda = 'PESO', $subfijo = 'M.N.')
    {
        $xarray = array(
            0 => 'Cero'
            , 1 => 'UN', 'DOS', 'TRES', 'CUATRO', 'CINCO', 'SEIS', 'SIETE', 'OCHO', 'NUEVE'
            , 'DIEZ', 'ONCE', 'DOCE', 'TRECE', 'CATORCE', 'QUINCE', 'DIECISEIS', 'DIECISIETE', 'DIECIOCHO', 'DIECINUEVE'
            , 'VEINTI', 30 => 'TREINTA', 40 => 'CUARENTA', 50 => 'CINCUENTA'
            , 60 => 'SESENTA', 70 => 'SETENTA', 80 => 'OCHENTA', 90 => 'NOVENTA'
            , 100 => 'CIENTO', 200 => 'DOSCIENTOS', 300 => 'TRESCIENTOS', 400 => 'CUATROCIENTOS', 500 => 'QUINIENTOS'
            , 600 => 'SEISCIENTOS', 700 => 'SETECIENTOS', 800 => 'OCHOCIENTOS', 900 => 'NOVECIENTOS'
        );

        $numero = trim($numero);
        $xpos_punto = strpos($numero, '.');
        $xaux_int = $numero;
        $xdecimales = '00';
        if (!($xpos_punto === false)) {
            if ($xpos_punto == 0) {
                $numero = '0' . $numero;
                $xpos_punto = strpos($numero, '.');
            }
            $xaux_int = substr($numero, 0, $xpos_punto); // obtengo el entero de la cifra a covertir
            $xdecimales = substr($numero . '00', $xpos_punto + 1, 2); // obtengo los valores decimales
        }

        $XAUX = str_pad($xaux_int, 18, ' ', STR_PAD_LEFT); // ajusto la longitud de la cifra, para que sea divisible por centenas de miles (grupos de 6)
        $xcadena = '';
        for ($xz = 0; $xz < 3; $xz++) {
            $xaux = substr($XAUX, $xz * 6, 6);
            $xi = 0;
            $xlimite = 6; // inicializo el contador de centenas xi y establezco el límite a 6 dígitos en la parte entera
            $xexit = true; // bandera para controlar el ciclo del While
            while ($xexit) {
                if ($xi == $xlimite) { // si ya llegó al límite máximo de enteros
                    break; // termina el ciclo
                }

                $x3digitos = ($xlimite - $xi) * -1; // comienzo con los tres primeros digitos de la cifra, comenzando por la izquierda
                $xaux = substr($xaux, $x3digitos, abs($x3digitos)); // obtengo la centena (los tres dígitos)
                for ($xy = 1; $xy < 4; $xy++) { // ciclo para revisar centenas, decenas y unidades, en ese orden
                    switch ($xy) {
                        case 1: // checa las centenas
                            $key = (int) substr($xaux, 0, 3);
                            if (100 > $key) { // si el grupo de tres dígitos es menor a una centena ( < 99) no hace nada y pasa a revisar las decenas
                                /* do nothing */
                            } else {
                                if (TRUE === array_key_exists($key, $xarray)) {  // busco si la centena es número redondo (100, 200, 300, 400, etc..)
                                    $xseek = $xarray[$key];
                                    $xsub = $this->_subfijo($xaux); // devuelve el subfijo correspondiente (Millón, Millones, Mil o nada)
                                    if (100 == $key) {
                                        $xcadena = ' ' . $xcadena . ' CIEN ' . $xsub;
                                    } else {
                                        $xcadena = ' ' . $xcadena . ' ' . $xseek . ' ' . $xsub;
                                    }
                                    $xy = 3; // la centena fue redonda, entonces termino el ciclo del for y ya no reviso decenas ni unidades
                                } else { // entra aquí si la centena no fue numero redondo (101, 253, 120, 980, etc.)
                                    $key = (int) substr($xaux, 0, 1) * 100;
                                    $xseek = $xarray[$key]; // toma el primer caracter de la centena y lo multiplica por cien y lo busca en el arreglo (para que busque 100,200,300, etc)
                                    $xcadena = ' ' . $xcadena . ' ' . $xseek;
                                } // ENDIF ($xseek)
                            } // ENDIF (substr($xaux, 0, 3) < 100)
                            break;
                        case 2: // checa las decenas (con la misma lógica que las centenas)
                            $key = (int) substr($xaux, 1, 2);
                            if (10 > $key) {
                                /* do nothing */
                            } else {
                                if (TRUE === array_key_exists($key, $xarray)) {
                                    $xseek = $xarray[$key];
                                    $xsub = $this->_subfijo($xaux);
                                    if (20 == $key) {
                                        $xcadena = ' ' . $xcadena . ' VEINTE ' . $xsub;
                                    } else {
                                        $xcadena = ' ' . $xcadena . ' ' . $xseek . ' ' . $xsub;
                                    }
                                    $xy = 3;
                                } else {
                                    $key = (int) substr($xaux, 1, 1) * 10;
                                    $xseek = $xarray[$key];
                                    if (20 == $key)
                                        $xcadena = ' ' . $xcadena . ' ' . $xseek;
                                    else
                                        $xcadena = ' ' . $xcadena . ' ' . $xseek . ' Y ';
                                } // ENDIF ($xseek)
                            } // ENDIF (substr($xaux, 1, 2) < 10)
                            break;
                        case 3: // checa las unidades
                            $key = (int) substr($xaux, 2, 1);
                            if (1 > $key) { // si la unidad es cero, ya no hace nada
                                /* do nothing */
                            } else {
                                $xseek = $xarray[$key]; // obtengo directamente el valor de la unidad (del uno al nueve)
                                $xsub = $this->_subfijo($xaux);
                                $xcadena = ' ' . $xcadena . ' ' . $xseek . ' ' . $xsub;
                            } // ENDIF (substr($xaux, 2, 1) < 1)
                            break;
                    } // END SWITCH
                } // END FOR
                $xi = $xi + 3;
            } // ENDDO
            # si la cadena obtenida termina en MILLON o BILLON, entonces le agrega al final la conjuncion DE
            if ('ILLON' == substr(trim($xcadena), -5, 5)) {
                $xcadena.= ' DE';
            }

            # si la cadena obtenida en MILLONES o BILLONES, entonces le agrega al final la conjuncion DE
            if ('ILLONES' == substr(trim($xcadena), -7, 7)) {
                $xcadena.= ' DE';
            }

            # depurar leyendas finales
            if ('' != trim($xaux)) {
                switch ($xz) {
                    case 0:
                        if ('1' == trim(substr($XAUX, $xz * 6, 6))) {
                            $xcadena.= 'UN BILLON ';
                        } else {
                            $xcadena.= ' BILLONES ';
                        }
                        break;
                    case 1:
                        if ('1' == trim(substr($XAUX, $xz * 6, 6))) {
                            $xcadena.= 'UN MILLON ';
                        } else {
                            $xcadena.= ' MILLONES ';
                        }
                        break;
                    case 2:
                        if (1 > $numero) {
                            $xcadena = "CERO {$moneda}S {$xdecimales}/100 {$subfijo}";
                        }
                        if ($numero >= 1 && $numero < 2) {
                            $xcadena = "UN {$moneda} {$xdecimales}/100 {$subfijo}";
                        }
                        if ($numero >= 2) {
                            $xcadena.= " {$moneda}S {$xdecimales}/100 {$subfijo}"; //
                        }
                        break;
                } // endswitch ($xz)
            } // ENDIF (trim($xaux) != "")

            $xcadena = str_replace('VEINTI ', 'VEINTI', $xcadena); // quito el espacio para el VEINTI, para que quede: VEINTICUATRO, VEINTIUN, VEINTIDOS, etc
            $xcadena = str_replace('  ', ' ', $xcadena); // quito espacios dobles
            $xcadena = str_replace('UN UN', 'UN', $xcadena); // quito la duplicidad
            $xcadena = str_replace('  ', ' ', $xcadena); // quito espacios dobles
            $xcadena = str_replace('BILLON DE MILLONES', 'BILLON DE', $xcadena); // corrigo la leyenda
            $xcadena = str_replace('BILLONES DE MILLONES', 'BILLONES DE', $xcadena); // corrigo la leyenda
            $xcadena = str_replace('DE UN', 'UN', $xcadena); // corrigo la leyenda
        } // ENDFOR ($xz)
        return trim($xcadena);
    }

    /**
     * Esta función regresa un subfijo para la cifra
     * 
     * @author Ultiminio Ramos Galán <contacto@ultiminioramos.com>
     * @param string $cifras La cifra a medir su longitud
     */
    private function _subfijo($cifras)
    {
        $cifras = trim($cifras);
        $strlen = strlen($cifras);
        $_sub = '';
        if (4 <= $strlen && 6 >= $strlen) {
            $_sub = 'MIL';
        }

        return $_sub;
    }

}

/*  EOF  */

El código fuente lo puedes descargar desde aquí poo_numtoletras.zip

Espero que les haya sido de utilidad.

domingo, 14 de abril de 2013

Cómo llamar un "config item" dentro de un helper en CodeIgniter

Este sencillo pero útil "tip" te va a ayudar a llamar un elemento de configuración (config item) desde un helper, una vista, un modelo, o algún otro tipo de archivo php que no sea un controlador (controller). Como sabemos y tal como la documentación de CodeIgniter lo indica, un config item se llama de la siguiente manera:
$xss_conf = $this->config->item('global_xss_filtering');
Pero, si hacemos esto en algún otro archivo php tal como una vista o un helper, recibiremos un error como éste:
Fatal error: Using $this when not in object context in translate_helper.php on line 31
Lo que debes hacer es usar la siguiente sintaxis:
$ci =& get_instance();
$xss_conf = $ci->config->item('global_xss_filtering');
Pero mejor aún, de una manera más sencilla, puedes usar:
$xss_conf = config_item('global_xss_filtering');

Espero que les haya sido de mucha utilidad como a mí.

La referencia a este tip está en: http://ellislab.com/forums/viewthread/130472/#643868

lunes, 14 de mayo de 2012

Ejecutar varias instancias de Firefox simultáneamente

Ejecutar varias instancias del navegador Firefox al mismo tiempo. Funciona para Windows XP, Windows Vista y Windows 7


Para los propósitos de este tutorial, voy a ejecutar tres versiones de Firefox, la 3.6, la 8, y la 12, pero de igual manera se pueden ejecutar diferentes instancias de la misma versión. En ambos casos, vamos a manejar los perfiles de Firefox.


Paso 1: 
Instalar las versiones que deseas en diferentes rutas, al momento de hacer la instalación, debes escoger la instalación Personalizada o Custom.

Datos personales

Mi foto
Podrás encontrar códigos recursos y artículos sobre PHP, JavaScript, jQuery, MooTools, Ajax, CSS, HTML, UML, RUP, AUP, XP (eXtreme Programming), Six-Sigma, CMMI, FrameWorks, Zend Framework, Magento, CodeIgniter, CakePHP, Joomla 1.5, Doctrine, Active Record, ORM, POO, MVC, MySql, PostgreSql. Este espacio está destinado a ayudar y compartir un poco de lo mucho que he recibido de la comunidad en la Red.