<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>fuga_digital</title>
	<atom:link href="https://fugadigital.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://fugadigital.com/</link>
	<description>Naturalmente digital</description>
	<lastBuildDate>Thu, 23 Mar 2023 22:44:51 +0000</lastBuildDate>
	<language>es</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.3</generator>
	<item>
		<title>Introducción práctica a Laravel: Tutorial para sistema de usuarios</title>
		<link>https://fugadigital.com/introduccion-practica-laravel-tutorial-sistema-de-usuarios/</link>
					<comments>https://fugadigital.com/introduccion-practica-laravel-tutorial-sistema-de-usuarios/#respond</comments>
		
		<dc:creator><![CDATA[Arturo]]></dc:creator>
		<pubDate>Fri, 20 Jun 2014 01:47:37 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Blade]]></category>
		<category><![CDATA[bundle]]></category>
		<category><![CDATA[framework]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[paquete]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Sentry]]></category>
		<guid isPermaLink="false">http://fugadigital.com/?p=56</guid>

					<description><![CDATA[<p>De algo de tiempo para acá Laravel ha empezado a hacerse realmente popular dentro del mundo de los frameworks en PHP. Esto se debe a la accesibilidad para usarlo ya que es funcional prácticamente después de instalarlo permitiendo construir de manera rápida una aplicación estándar. Además, al utilizar componentes que han probado su robustez (Oh &#8230; </p>
<p class="link-more"><a href="https://fugadigital.com/introduccion-practica-laravel-tutorial-sistema-de-usuarios/" class="more-link">Continuar leyendo<span class="screen-reader-text"> "Introducción práctica a Laravel: Tutorial para sistema de usuarios"</span></a></p>
<p>La entrada <a href="https://fugadigital.com/introduccion-practica-laravel-tutorial-sistema-de-usuarios/">Introducción práctica a Laravel: Tutorial para sistema de usuarios</a> se publicó primero en <a href="https://fugadigital.com">fuga_digital</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><a href="https://fugadigital.com/wp-content/uploads/2014/06/laravel_arrived.png"><img fetchpriority="high" decoding="async" src="https://fugadigital.com/wp-content/uploads/2014/06/laravel_arrived.png" alt="Laravel: You have arrived." width="300" height="506" class="aligncenter size-full wp-image-57" srcset="https://fugadigital.com/wp-content/uploads/2014/06/laravel_arrived.png 300w, https://fugadigital.com/wp-content/uploads/2014/06/laravel_arrived-177x300.png 177w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
<p>De algo de tiempo para acá Laravel ha empezado a hacerse realmente popular dentro del mundo de los frameworks en PHP. Esto se debe a la accesibilidad para usarlo ya que es <a href="https://laravel.com/docs/installation#configuration">funcional prácticamente después de instalarlo</a> permitiendo construir de manera rápida una aplicación estándar. Además, al utilizar componentes que han probado su robustez (Oh gran Symfony) en lugar de reinventar la rueda, Laravel ofrece un entorno de desarrollo sencillo montado sobre bases sólidas.</p>
<p>Normalmente los tutoriales para aprender a usar un framework se enfocan en la creación del obligado blog. Evidentemente necesitaremos un mecanismo de acceso para algo así de modo que esta guía contiene las instrucciones para crear el sistema de logueo y registro que podremos usar más adelante al crear nuestro blog, CMS, o cualquier otra aplicación.</p>
<p><span id="more-56"></span></p>
<p>Si has empezado a leer las introducciones en la documentación oficial verás que desde el inicio te envían a crear rutas, generar alguna vista y ver resultados. Más rápido no podría ser, sin embargo si ya has usado otros frameworks todo esto ya empezará a sonar algo raro. No temas, Laravel 4 permite nativamente la creación de paquetes (llamados anteriormente bundles) para mantener nuestro código con el acoplamiento y cohesión adecuados.</p>
<p>Para que nuestro primer paquete sea realmente útil en un futuro lo implementaremos usando una gran librería para la tarea: <a href="https://cartalyst.com/manual/sentry">Cartalyst Sentry</a>. Sentry implementa de muy buena manera un sistema de usuarios. Aún cuando en este tutorial no haremos nada avanzado, nos facilitará implementar mejoras conforme nuestras necesidades lo requieran (grupos, permisos, bloqueos, etc.).</p>
<p>Nota: Se da por hecho que tienes un entorno listo: servidor local, PHP>=5.4, RDBMS, Composer y Laravel accesible desde tu navegador (You have arrived.). Puedes seguir la guía de instalación para trabajar con Vagrant y Homestead, que es una máquina virtual con todo lo necesario para Laravel.</p>
<p>Antes de comenzar necesitamos configurar el creador de paquetes editando <em>&#8230;/app/config/workbench.php</em> agregando nombre y dirección electrónica, y el acceso a la base de datos que usará el framework, editando <em>&#8230;/app/config/database.php</em>.</p>
<p>Para trabajar en Homestead lo siguiente puede ser útil:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">'mysql' =&gt; array(
			'driver'    =&gt; 'mysql',
			'host'      =&gt; '127.0.0.1',
			'port'      =&gt; 33060,
			'database'  =&gt; 'homestead',
			'username'  =&gt; 'homestead',
			'password'  =&gt; 'secret',
			'charset'   =&gt; 'utf8',
			'collation' =&gt; 'utf8_unicode_ci',
			'prefix'    =&gt; '',
		),

	mysql -h127.0.0.1 homestead -P33060 -uhomestead -p</pre><p></p>
<p>Ya que tenemos configurado el acceso a nuestra base, la prepararemos para migrar (crear tablas e importar datos), esto lo haremos ejectuando:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan migrate:install</pre><p></p>
<p>que nos creará la tabla de control de migraciones.</p>
<p>Comenzaremos creando el esqueleto de nuestro paquete de autenticación:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan workbench allegro/auth</pre><p></p>
<p>Ejecutando la línea anterior desde la terminal en la ruta base donde está Laravel, <code>artisan</code> creará una <a href="http://laravel.com/docs/packages#creating-a-package">estructura estandar</a> para un paquete. Si revisamos el directorio raiz de Laravel veremos la siguiente estructura de directorios creada:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">laravel_root
    ...
    workbench/
        allegro/
            auth/
                ...</pre><p></p>
<p>Dentro de <em>workbench</em> se encontrarán todos los paquetes que hagamos. <em>allegro</em> es el vendor que puedes/debes cambiarlo por tu nickname, usuario de Git, compañía, palabra impactante, etc; <em>auth</em> es el paquete en específico que estamos creando.</p>
<p>Si revisas el contenido en <em>&#8230;/workbench/allegro/auth/</em> verás que la estructura del paquete es realmente sencilla, más adelante iremos agregando algunas cosas.</p>
<p>Lo primero que haremos será configurar el composer.json de nuestro paquete:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">{
    "name": "allegro/auth",
    "description": "A simple authentication implementation using Sentry",
    "license": "MIT",
    "authors": [
        {
            "name": "John Doe",
            "email": "john.doe@domain.com"
        }
    ],
    "require": {
        "php": ">=5.4.0",
        "illuminate/support": "4.2.*",
        "cartalyst/sentry": "2.1.*"
    },
    "autoload": {
        "psr-0": {
            "Allegro\\Auth": "src/"
        }
    },
    "minimum-stability": "stable"
}</pre><p></p>
<p>Agregamos una licencia, una descripción e indicamos que vamos a utilizar el paquete Sentry. Luego de guardar los cambios actualizaremos las dependencias ejecutando en el directorio raiz de nuestro paquete (&#8230;/workbench/allegro/auth/)</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">composer update</pre><p></p>
<p>Una vez que se haya descargado Sentry publicamos su configuración en Laravel y creamos la base de  datos.</p>
<p>Para la parte de configuración abriremos el archivo <em>&#8230;/app/config/app.php</em> y agregaremos el nombre de la clase de <em>Sentry</em> al arreglo de proveedores y su alias al arreglo de aliases:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">'providers' =&gt; array(
		...
		'Cartalyst\Sentry\SentryServiceProvider',
	...
	'aliases' =&gt; array(
		...
		'Sentry'          =&gt; 'Cartalyst\Sentry\Facades\Laravel\Sentry',</pre><p></p>
<p>después debemos exponer el archivo de configuración. Como estamos agregando la dependencia a un paquete y no directamente al framework debemos definir explícitamente su ubicación.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan config:publish --path=&quot;workbench/allegro/auth/vendor/cartalyst/sentry/src/config&quot; cartalyst/sentry</pre><p></p>
<p>Ya sólo queda generar las tablas en la base:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan migrate --path=&quot;workbench/allegro/auth/vendor/cartalyst/sentry/src/migrations/&quot; --force</pre><p></p>
<p>Con lo anterior tenemos la dependencia lista para ser usada, de lujo eh. Vayamos ahora a configurar nuestro paquete.</p>
<p>Primero vamos a editar la clase <em>&#8230;/workbench/allegro/auth/src/Allegro/Auth/AuthServiceProvider.php</em> que es la clase de arranque agregando el método <code>boot()</code>:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">...
    protected $defer = false;

    /**
     * Bootstrap the application events.
     *
     * @return void
     */
    public function boot()
    {
        $this->package('allegro/auth');
        include __DIR__.'/../../routes.php';
    }
    ...
}</pre><p></p>
<p>La primera línea en <code>boot()</code> establece como identificará el framework nuestro paquete, la segunda carga el archivo (inexistente hasta este momento) que contendrá la definición de nuestras rutas.</p>
<p>El paquete ya puede ser cargado por Laravel y sólo falta registrarlo en él para activarlo, esto se hace abriendo el archivo <em>&#8230;/app/config/app.php</em> y agregando al arreglo de proveedores como lo hicimos con Sentry:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">'providers' =&gt; array(
		...
		'Cartalyst\Sentry\SentryServiceProvider',
		'Allegro\Auth\AuthServiceProvider',</pre><p></p>
<p>Ahora vamos a crear el archivo de rutas <em>routes.php</em> dentro de la raiz de nuestro paquete (<em>&#8230;/workbench/allegro/auth</em>) y definir las rutas de acceso al paquete. Esto nos dará una visión general de que tiene que existir.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><?php

$sessionController = 'Allegro\Auth\Controller\SessionController';
$userController    = 'Allegro\Auth\Controller\UserController';

/**
* Checks if the user is logged
*/
Route::filter('sentry.auth', function()
{
    if (!Sentry::check()) {
        Session::put('loginRedirect', Request::url());
        return Redirect::route('user_login_show');
    }
});

// // // // // // // // // // // // // // // // // // // // // // Session routes

Route::get('login', [
    'as'         => 'user_login_show',
    'uses'       => "$sessionController@create"
]);

Route::post('login', [
    'as'         => 'user_login_action',
    'uses'       => "$sessionController@store"
]);

Route::get('logout', [
    'as'         => 'user_logout',
    'uses'       => "$sessionController@destroy"
]);

// // // // // // // // // // // // // // // // // // // // // // // User routes

Route::get('register', [
    'as'         => 'user_register_show',
    'uses'       => "$userController@create"
]);

Route::post('register', [
    'as'         => 'user_register_action',
    'uses'       => "$userController@store"
]);

Route::get('user/edit', [
    'before'     => 'sentry.auth',
    'as'         => 'user_edit_show',
    'uses'       => "$userController@edit"
]);

Route::post('user/edit', [
    'before'     => 'sentry.auth',
    'as'         => 'user_edit_action',
    'uses'       => "$userController@update"
]);

Route::get('user/delete', [
    'before'     => 'sentry.auth',
    'as'         => 'user_delete',
    'uses'       => "$userController@destroy"
]);</pre><p></p>
<p>Si has leído un poco de <a href="http://laravel.com/docs/routing">enrutamiento en Laravel</a> sabrás que hay diferentes <a href="http://laravel.com/docs/controllers">estrategias disponibles</a> para acceder a los controladores, sin embargo las estrategias automáticas (<a href="http://philsturgeon.co.uk/blog/2013/07/beware-the-route-to-evil">REST</a>, <a href="http://forumsarchive.laravel.io/viewtopic.php?id=6769">recursos</a>) limitan el manejo de las rutas al usar enrutamientos inversos y hacen menos evidentes los accesos a nuestro sistema, así que usaremos el método básico para definir explícitamente todos nuestros accesos y tener un control total, de cualquier manera usaremos los nombres estándar para <a href="laravel.com/docs/controllers#resource-controllers">las acciones</a>.</p>
<p>Personalmente uso namespaces siempre que puedo. Si no los usas hay <a href="http://daylerees.com/php-namespaces-explained">buenas razones</a> para empezar a hacerlo, aunque esto significa que necesitaremos agregar <em>\</em> como prefijo a cada fachada usada para evitar que sea interpretada como parte de nuestro paquete (ej. <code>\BaseController::</code> en lugar de <code>BaseController::</code>, <code>\Input::</code> en lugar de <code>Input::</code>, etc). Siendo consistentes también con la propuesta de uso de namespaces que en <em>AuthServiceProvider.php</em> usaremos controladores con namespaces.</p>
<p>Podríamos también realizar el trabajo de los controladores dentro de las propias funciones de enrutamiento agregando clousures, sin embargo por razones de separación de concernimientos usaremos <em>routes.php</em> para definir los accessos y <em>SessionController.php / UserController.php</em> para la acciones correspondientes al acceso y gestión.</p>
<p>Ahora vamos a crear los controladores que procesarán las solicitudes que acabamos de definir.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan controller:make --bench allegro/auth UserController --only=create,store,edit,update,destroy</pre><p></p>
<p>creará el controlador de usuarios y</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan controller:make --bench allegro/auth SessionController --only=create,store,destroy</pre><p></p>
<p>creará el controlador de sesiones.</p>
<p>Como no usaremos todas las acciones REST indicamos de manera explícita el subconjunto de métodos que nos interesa que sean maquetados. Nuestros nuevos controladores han sido creados y depositados dentro del (nuevo) subdirectorio <em>controllers</em>.</p>
<p>La clase <em>UserController</em> necesita el siguiente código:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;?php namespace Allegro\Auth\Controller;

class UserController extends \BaseController {

    /**
     * Show the form for creating a new resource.
     *
     * @return Response
     */
    public function create()
    {
        return \View::make('auth::user.register');
    }


    /**
     * Store a newly created resource in storage.
     *
     * @return Response
     */
    public function store()
    {
        try {
            $p1 = \Input::get('password');
            $p2 = \Input::get('password_r');
            if ('' === $p1 || '' === $p2 || $p1 !== $p2) {
                throw new \Exception('Passwords don\'t match');
            }

            \Sentry::createUser([
                'email'         =&gt; \Input::get('email'),
                'password'      =&gt; \Input::get('password'),
                'activated'     =&gt; true,
                'created_at'    =&gt; date('Y-m-d H:i:s'),
                'updated_at'    =&gt; date('Y-m-d H:i:s'),
            ]);
        }
        /* @var $ex \Exception */
        catch (\Exception $ex) {
            return \Redirect::route('user_register_show')
                -&gt;withInput()
                -&gt;with('error', $ex-&gt;getMessage());
        }

        return \Redirect::route('user_login_show')
                -&gt;with('notice', 'User has been created');
    }


    /**
     * Show the form for editing the specified resource.
     *
     * @return Response
     */
    public function edit()
    {
        return \View::make('auth::user.edit');
    }


    /**
     * Update the specified resource in storage.
     *
     * @return Response
     */
    public function update()
    {
        try {
            /* @var $user \Cartalyst\Sentry\Users\Eloquent\User */
            $user = \Sentry::getUser();

            $user-&gt;first_name = \Input::get('first_name');
            $user-&gt;last_name = \Input::get('last_name');

            if ('' !== \Input::get('password') . \Input::get('password_r')) {
                $p1 = \Input::get('password');
                $p2 = \Input::get('password_r');
                if ($p1 !== $p2) {
                    throw new \Exception('Passwords don\'t match');
                }
                $user-&gt;password = $p1;
                $logout = true;
            }
            if (!$user-&gt;update()) {
                throw new \Exception('Unable to update values');
            }
        }
        /* @var $ex \Exception */
        catch (\Exception $ex) {
            return \Redirect::route('user_edit_show')
                -&gt;withInput()
                -&gt;with('error', $ex-&gt;getMessage());
        }

        if (isset($logout)) {
            \Sentry::logout();
            return \Redirect::route('user_login_show')
                    -&gt;with('notice', 'User has been updated, please log in again');
        }

        return \Redirect::back()
                -&gt;with('notice', 'User has been updated');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @return Response
     */
    public function destroy()
    {
        /* @var $user \Cartalyst\Sentry\Users\Eloquent\User */
        $user = \Sentry::getUser();
        $user-&gt;delete();
        \Sentry::logout();
        return \Redirect::route('user_login_show')
                -&gt;with('notice', 'User has been deleted. Wanna check?');
    }
}</pre><p></p>
<p>y la clase <em>SessionController</em>:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;?php namespace Allegro\Auth\Controller;

class SessionController extends \BaseController {

    /**
     * Show the login page
     * @return View
     */
    public function create()
    {
        return \View::make('auth::session.login');
    }

    /**
    * Store a newly created resource in storage.
    *
    * @return Response
    */
    public function store()
    {
        $credentials = [
            'email'        =&gt; \Input::get('email'),
            'password'     =&gt; \Input::get('password'),
        ];
        $remember = \Input::get('remember') === '1';

        try {
            \Sentry::authenticate($credentials, $remember);
        }
        catch (\Exception $ex) {
            $msg = $ex instanceof \Cartalyst\Sentry\Throttling\UserSuspendedException
                    ? $ex-&gt;getMessage()
                    : 'Invalid credentials';

            return \Redirect::back()
                    -&gt;withInput()
                    -&gt;with('error', $msg);
        }

        // Extract requested page from session
        $redirect = \Session::get('loginRedirect', \URL::route('user_edit_show'));
        \Session::forget('loginRedirect');

        return \Redirect::to($redirect);
    }

    /**
    * Remove the specified resource from storage.
    *
    * @return Response
    */
    public function destroy()
    {
        \Sentry::logout();
        \Session::flash('notice', 'Session closed');
        return \Redirect::back();
    }

}</pre><p></p>
<p>Algo a notar es que los parámetros que recibían algunos métodos se han removido pues no son útiles en nuestro caso.</p>
<p>Laravel usa Composer para la carga automática de clases, así que para que nuestros controladores sean usables debemos hacer dos cosas: agregar al <em>composer.json</em> del paquete la sección <code>classmap</code> con el directorio <em>controllers</em> y luego ejecutar desde la raiz de nuestro paquete el comando de actualización de carga automática de clase.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">&quot;autoload&quot;: {
        &quot;classmap&quot;: [
            &quot;src/controllers&quot;
        ],
        &quot;psr-0&quot;: {
            &quot;Allegro\\Auth&quot;: &quot;src/&quot;
        }
    },</pre><p></p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">composer dump-autoload</pre><p></p>
<p><a href="http://laravel.com/docs/packages#creating-a-package">Nota</a>: Si el proveedor de servicios no se puede encontrar, ejecuta el comando <code>php artisan dump-autoload</code> desde el directorio raiz de tu aplicación.</p>
<p>Con lo que hemos hecho hasta aquí ya tenemos el back-end prácticamente listo y sólo necesitamos crear algunas vistas para nuestro front-end.</p>
<p>El motor de plantillas de Laravel es bastante sencillo pero lo suficientemente potente para crear templates reutilizables y extensibles permitiéndonos reciclar código. Revisando los controladores vemos que tenemos algunas líneas como <code>return \View::make('auth::user.register');</code> que podemos interpretar como «Hey amigo, carga la vista auth/views/user/register[.blade].php».</p>
<p>La estructura de plantillas que corresponde a los llamados de los controladores (y que debemos crear) es la siguiente:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">auth/
    public/
        style.css
    src/
        views/
            base.blade.php
            user_box.blade.php
            session/
                login.blade.php
            user/
                edit.blade.php
                register.blade.php
    ...</pre><p></p>
<p><em>base.blade.php</em> contendrá la plantilla general para todas las vistas, excepto <em>user_box.blade.php</em> (que será importada por otros paquetes para mostrar una cajita que despliegue al usuario activo y ligas de inicio y cierre de sesión). Los demás archivos no necesitan más explicación.</p>
<p>base.blade.php:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Userland</title>
        <link rel="stylesheet" type="text/css" href="{{ URL::asset('packages/allegro/auth/style.css') }}" />
    </head>
    <body>
        @if(Session::has('error'))
            <p class="alert {{ Session::get('alert-class', 'alert-error') }}">{{ Session::get('error') }}</p>
        @endif
        @if(Session::has('notice'))
            <p class="alert {{ Session::get('alert-class', 'alert-info') }}">{{ Session::get('notice') }}</p>
        @endif

        <div class="container">
            <div id="login">
                @yield('main')
            </div>
        </div>
    </body>
</html></pre><p></p>
<p>user_box.blade.php:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><div class="auth-user-box">
    @if (!Sentry::check())
    <a class="brand" href="{{ URL::route('user_login_show') }}">Sign in</a>
    @else
    <a href="{{ URL::route('user_edit_show') }}">{{ Sentry::getUSer()->getLogin() }}</a> / <a href="{{ URL::route('user_logout') }}">Sign out</a>
    @endif
</div></pre><p></p>
<p>register.blade.php:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">@extends('auth::base')

@section('main')
{{ Form::open() }}
    <div class="title"><div>
        Register
    </div></div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('email', 'Email') }}
        </div>
        <div class="field">
            {{ Form::text('email') }}
        </div>
    </div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('password', 'Password') }}
        </div>
        <div class="field">
            {{ Form::password('password') }}
        </div>
    </div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('password_r', 'Repeat pwd') }}
        </div>
        <div class="field">
            {{ Form::password('password_r') }}
        </div>
    </div>

    <div class="form-actions">
        <a href="{{ URL::route('user_login_show') }}" class="btn">Sign in</a>
        &nbsp;
        {{ Form::submit('Register', array('class' => 'btn')) }}
    </div>
{{ Form::close() }}
@stop</pre><p></p>
<p>edit.blade.php:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">@extends('auth::base')

@section('main')
<?php
    function parseDate($date)
    {
        $dateTime = DateTime::createFromFormat('Y-m-d H:i:s', $date);
        return $dateTime->format('D d, M Y, H:i');
    }
?>
{{ Form::open() }}
    <div class="title"><div>
        Edit
    </div></div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('login', 'Login') }}
        </div>
        <div class="field"><div>
            {{ Sentry::getUser()->getLogin() }}
        </div></div>
    </div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('firs_name', 'first name') }}
        </div>
        <div class="field">
            {{ Form::text('first_name', Sentry::getUser()['attributes']['first_name']) }}
        </div>
    </div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('last_name', 'last name') }}
        </div>
        <div class="field">
            {{ Form::text('last_name', Sentry::getUser()['attributes']['last_name']) }}
        </div>
    </div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('password', 'Password') }}
        </div>
        <div class="field">
            {{ Form::password('password') }}
        </div>
    </div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('password_r', 'Repeat pwd') }}
        </div>
        <div class="field">
            {{ Form::password('password_r') }}
        </div>
    </div>

    <div class="form-actions">
        <a href="{{ URL::previous() }}" class="btn">Cancel</a>
        &nbsp;
        {{ Form::submit('Update', array('class' => 'btn')) }}
        <p>Registered at: {{ parseDate(Sentry::getUser()['attributes']['created_at']) }}</p>
        <p>Last login: {{ parseDate(Sentry::getUser()['attributes']['last_login']) }}</p>
        <p><a href="{{ URL::route('user_logout') }}" class="btn">Sign out</a></p>
        <br>
        <p><a href="{{ URL::route('user_delete') }}" class="btn">Delete account</a></p>
    </div>
{{ Form::close() }}
@stop</pre><p></p>
<p>login.blade.php:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">@extends('auth::base')

@section('main')
    @if (!Sentry::check())
        {{ Form::open() }}
        <div class="title"><div>
            Sign in
        </div></div>
        <div class="control-group">
            <div class="label">
                {{ Form::label('email', 'Email') }}
            </div>
            <div class="field">
                {{ Form::text('email') }}
            </div>
        </div>

        <div class="control-group">
            <div class="label">
                {{ Form::label('password', 'Password') }}
            </div>
            <div class="field">
                {{ Form::password('password') }}
            </div>
        </div>

        <div class="control-group">
            <div class="label">
            {{ Form::label('remember', 'remember') }}
            </div>
            <div class="field"><div>
                {{ Form::checkbox('remember') }}
            </div></div>
        </div>

        <div class="form-actions">
            <a href="{{ URL::route('user_register_show') }}" class="btn">Register</a>
            &nbsp;
            <input type="submit" class="btn" name="login" value="Sign in">
        </div>
        {{ Form::close() }}
    @else
        <div class="center-text">
            You already signed in as
            <br><br>
            {{ Sentry::getUSer()->getLogin() }}
            <br><br>
            <a href="{{ URL::route('user_edit_show') }}" class="btn">Edit</a>
            &nbsp;
            <a href="{{ URL::route('user_logout') }}" class="btn">Sign out</a>
        </div>
    @endif
@stop</pre><p></p>
<p>style.css:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">html {
    height: 100%;
}
body {
    background-color: #eee;
    margin: 0;
    padding: 0;
    font-family: sans-serif;
    height: 100%;
}
p.alert {
    text-align: center;
    font-weight: bold;
    padding: 10pt;
    margin: 0;
    background-color: #888;
    color: #fff;
}
p.alert-error {
    background-color: #ff3333;
}
.center-text {
    text-align: center;
}
.container {
}
#login-block {
    background-color: #f9f9f9;
    margin: 0 auto;
    padding: 10pt;
    max-width: 375px;
    border-radius: 0 0 10px 10px;
    border: 1px solid #ddd;
    border-top: 0;
}
.title {
    background: #85cf85;
    margin: -10pt -10pt 20pt -10pt;
    font-size: 200%;
    text-align: center;
}
.title > * {
    padding: 5pt;
}
.control-group {
    clear: both;
    margin: 10pt 0;
    overflow: hidden;
}
.label {
    float: left;
    vertical-align: middle;
}
.field {
    float:right;
    width: 65%;
    overflow: hidden;
    text-align: right;
}
.field > * {
    width: 97%;
    font-size: 100%;
    text-align: left;
}
.form-actions {
    text-align: right;
}
.btn {
    border-radius: 4pt;
    background: #ddd;
    color: #444;
    font-weight: bold;
    font-size: 100%;
    padding: 2px 8px;
}</pre><p></p>
<p>Ahora solo resta hacer público nuestro archivo de estilos. Para eso nos vamos a nuestra consola y ejecutamos en la raiz de nuestro proyecto:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan asset:publish --bench allegro/auth</pre><p></p>
<p>Ahora si, tenemos todo listo.</p>
<p>Como has visto la creación de paquetes en Laravel no tiene complicaciones. Al ser éste un tutorial práctico introductorio, he tratado de mostrar una estructura adecuada y a la vez sencilla, que sirva de ejemplo y que además sea útil en un futuro y no sólo como un ejercicio, sin embargo hay algunas modificaciones que podrían venir bien, como mover el filtro que se encuentra en el controlador a un archivo de filtros y cargarlo junto con <em>routes.php</em> en la clase proveedora, o seccionar de mejor manera las vistas, etc.</p>
<p>El paquete completo puedes <a href="https://github.com/fugadigital/SimpleLaravel4AuthPackage">descargarlo desde Github</a></p>
<p>Feliz Laraveleo.</p>
<p>La entrada <a href="https://fugadigital.com/introduccion-practica-laravel-tutorial-sistema-de-usuarios/">Introducción práctica a Laravel: Tutorial para sistema de usuarios</a> se publicó primero en <a href="https://fugadigital.com">fuga_digital</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://fugadigital.com/introduccion-practica-laravel-tutorial-sistema-de-usuarios/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Agregar campos informativos en formularios de Sonata Admin</title>
		<link>https://fugadigital.com/agregar-campos-informativos-en-formularios-de-sonata-admin/</link>
					<comments>https://fugadigital.com/agregar-campos-informativos-en-formularios-de-sonata-admin/#respond</comments>
		
		<dc:creator><![CDATA[Arturo]]></dc:creator>
		<pubDate>Mon, 12 May 2014 14:45:20 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Sonata]]></category>
		<category><![CDATA[SonataAdminBundle]]></category>
		<category><![CDATA[Symfony2]]></category>
		<category><![CDATA[Twig]]></category>
		<guid isPermaLink="false">http://fugadigital.com/?p=34</guid>

					<description><![CDATA[<p>No es sorpresa la gran popularidad que tiene el bundle Sonata Admin, siendo como dicen ellos «El generador de admistradores faltante en Symfony2» (énfasis mío). Al generar los adminstradores de nuestras entidades es común necesitar desplegar junto con los campos a editar, campos informativos del objeto (fecha de creación, último acceso, autor, etc.). Se puede &#8230; </p>
<p class="link-more"><a href="https://fugadigital.com/agregar-campos-informativos-en-formularios-de-sonata-admin/" class="more-link">Continuar leyendo<span class="screen-reader-text"> "Agregar campos informativos en formularios de Sonata Admin"</span></a></p>
<p>La entrada <a href="https://fugadigital.com/agregar-campos-informativos-en-formularios-de-sonata-admin/">Agregar campos informativos en formularios de Sonata Admin</a> se publicó primero en <a href="https://fugadigital.com">fuga_digital</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>No es sorpresa la gran popularidad que tiene el bundle <em>Sonata Admin</em>, siendo como dicen ellos «El generador de admistradores faltante en Symfony2» (énfasis mío).</p>
<p>Al generar los adminstradores de nuestras entidades es común necesitar desplegar junto con los campos a editar, campos informativos del objeto (fecha de creación, último acceso, autor, etc.). Se puede fácilmente agregar los campos y hacerlos no editables y/o desactivarlos, pero visualmente el resultado no es realmente bueno.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">// simple but ugly
->add('created', null, array(
    'read_only' => true,
    'disabled' => true,
    'widget' => 'single_text',
    'format' => 'EEE, MMM FF yyyy HH:mm'
))</pre><p></p>
<p>Podemos utilizar CSS para mejorar considerablemente la imagen, sin embargo esto no funciona si queremos desplegar estructuras más complejas.</p>
<p><span id="more-34"></span></p>
<p>Tratándose de <em>Symfony2</em> y <em>SonataAdmin</em> es posible adaptar los administradores generados para nuestro bundle en la medida que necesitemos sin impactar en otras áreas. En esta ocasión lo aprovecharemos para crear etiquetas informativas insertables como campos dentro de los formularios.</p>
<p><a href="https://fugadigital.com/wp-content/uploads/2014/05/labeled_cat.jpg"><img decoding="async" src="https://fugadigital.com/wp-content/uploads/2014/05/labeled_cat-300x224.jpg" alt="label in cat" width="300" height="224" class="aligncenter size-medium wp-image-35" srcset="https://fugadigital.com/wp-content/uploads/2014/05/labeled_cat-300x224.jpg 300w, https://fugadigital.com/wp-content/uploads/2014/05/labeled_cat.jpg 500w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
<p>Probado con:<br />
Symfony 2.4<br />
Sonata Admin Bundle 2.2</p>
<p>Dependiendo de los objetivos, lo que hacer es:<br />
&#8211; <a href="#type-class">Crear la clase para el nuevo tipo de despliegue</a><br />
&#8211; <a href="#transformer">Crear un transformador para hacer desplegable el objeto</a><br />
&#8211; <a href="#admin-config">Configurar las clases de administración con el objeto y el nuevo tipo</a><br />
&#8211; <a href="#twig">Crear los bloques en Twig con el marcado para el despliegue</a></p>
<p>Lo siguiente mostrará como desplegar un objecto DateTime, pero la guía es fácilmente adaptable para otros tipos, como números, hipervínculos, entidades, colecciones o cualquier cosa.</p>
<h3 id="type-class">Crear la clase para el nuevo tipo de despliegue</h3>
<p>Primero necesitamos crear una clase para el nuevo tipo</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">/* Acme/DemoBundle/Form/StaticTextType.php */
<?php

namespace Acme\DemoBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class StaticTextType extends AbstractType
{
    public function getParent()
    {
        return 'text';
    }

    public function getName()
    {
        return 'acme_static_text';
    }
}</pre><p></p>
<p>Para usar el nuevo tipo podemos incluir la clase e instanciarla en el administrador</p><pre class="urvanov-syntax-highlighter-plain-tag">/* Acme/DemoBundle/Admin/EntityAdmin.php */
<?php
namespace Acme\DemoBundle\Admin;

...
use Acme\DemoBundle\Form\StaticTextType;
... 

...
    ->add('id', new StaticTextType())
...</pre><p></p>
<p>o crear crear un servicio y registrarlo como nuevo tipo de formulario. Esto también permite hacer uso de él a través de su etiqueta.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"># Acme/DemoBundle/Resources/config/services.yml
services:
    ...
    acme_demo.form.type.info:
        class: Acme\DemoBundle\Form\StaticTextType
        tags:
            - { name: form.type, alias: acme_static_text }</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">/* Acme/DemoBundle/Admin/EntityAdmin.php */
    ...
    ->add('id', 'acme_static_text')</pre><p></p>
<p>Para desplegar información básica como números o cadenas no necesitamos crear el transformador pues nuestro tipo hereda las características del tipo &#8216;text&#8217;.</p>
<h3 id="transformer">Crear un transformador para hacer desplegable el objeto</h3>
<p>Para desplegar la fecha y hora necesitamos un transformador que genere la cadena que represente a la instancia <code>DateTime</code>.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">/* Acme/DemoBundle/Form/DataTransformer/DateTimeToStringTransformer.php */
<?php
namespace Acme\DemoBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;

class DateTimeToStringTransformer implements DataTransformerInterface
{
    /**
     * The format for our DateTime object
     * @var string
     */
    private $format;
    
    public function __construct($format)
    {
        $this->format = $format;
    }
    
    /**
     * Transforms a datetime object to a string.
     *
     * @param  DateTime|null $date
     * @return string
     */
    public function transform($dateTime)
    {
        return null === $dateTime
	    ? return ''
	    : $dateTime->format($this->format);
    }

    /**
     * Transforms a string date to a DateTime object.
     *
     * @param  string $dateTime
     *
     * @return DateTime|null
     *
     * @throws TransformationFailedException if cannot transform to DateTime.
     */
    public function reverseTransform($dateTime)
    {
        if (empty($dateTime)) {
            return null;
        }

        $dt = DateTime::createFromFormat($this->format, $dateTime);
        if (null === $dt) {
            throw new TransformationFailedException(sprintf(
                "Unable to transform '%s' to a DateTime object",
                $dateTime
            ));
        }

        return $dt;
    }
}</pre><p></p>
<h3 id="admin-config">Configurar las clases de administración con el objeto y el nuevo tipo</h3>
<p>Para mostrar los datos de los campos de manera que no sean procesados por el formulario, podemos agregar el campo configurándolo como no mapeado y el dato manualmente agregado o configurarlo como desactivado.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><?php
    // correct display, error on save
    ->add('id', 'acme_static_text', array(
        'required' => false
    ))

    // correct display, ignored on save
    ->add('id', 'acme_static_text', array(
        'required' => false,
        'disabled' => true,
    ))

    $entity = $this->getSubject();
    ->add('id', 'acme_static_text', array(
        'required' => false,
        'mapped' => false,
        'data' => $entity->getId(),
    ))</pre><p></p>
<p>Tip: Como el método <code>$this->getSubject()</code> de la clase de administración regresa la entidad que se está gestionando, tambíen se puede aprovechar para desplegar diferentes campos dependiendo de si se está creando una nueva instancia o editando una existente.</p>
<p>Nota: Al utilizar subadminstradores <code>getSubject()</code> regresa siempre la primera entidad.</p>
<p>Para usar el transformador al estructurar nuestro formulario en el administrador no agregamos directamente nuestro objeto con el tipo, sino que creamos un objeto <code>FormBuilder</code> al que le agregaremos una instancia del transformador.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">/* Acme/DemoBundle/Admin/EntityAdmin.php */
<?php
    namespace Acme\DemoBundle\Admin;

    ...
    use Acme\DemoBundle\Form\DataTransformer\DateTimeToStringTransformer;
    ...

        // Static text showing the DateTime object
        ->add(
            $formMapper->create('created', 'acme_static_text', array(
                // data_class set to null to avoid object type errors
                'data_class' => null,
                'required' => false,
                'disablex' => true,
            ))
            ->addViewTransformer(new DateTimeToStringTransformer('D, M d Y H:i:s'))
        )</pre><p></p>
<p>Algunos otros ejemplos de configuración:</p><pre class="urvanov-syntax-highlighter-plain-tag">// 1-to-N collection would be something like this
        ->add('pages', 'acme_static_text_collection', array(
            'label' => false,
            'type' => 'entity',
                'options' => array(
                'class' => 'AcmeDemoBundle:Foo',
            ),
        ))

        // An entity would look like
        ->add(
            $formMapper->create('author', 'info', array(
                'data_class' => null,
                'required' => false,
                'mapped' => false,
                'data' => $subject->getAuthor(),
                'property_path' => null
            ))
            ->addViewTransformer(new UserToStringTransformer())
        )</pre><p></p>
<h3 id="twig">Crear los bloques en Twig con el marcado para el despliegue</h3>
<p>Finalmente vamos a construir los widgets con el HTML para mostrar la información. Como nuestro tipo se llama <em>acme_static_text</em> el bloque lo debemos crear con el nombre <em>acme_static_text_widget</em>.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">{# Acme/DemoBundle/Resources/views/Form/widgets.html.twig #}
{% block acme_static_text_widget %}
    {% spaceless %}
        <div class="sonata-ba-field sonata-ba-field-standard-natural">{{ value }}</div>
    {% endspaceless %}
{% endblock %}</pre><p></p>
<p>Ahora debemos agregar nuestra plantilla con widgets en el administrador para que pueda ser usada. Una manera sencilla es sobrecargando el método <code>getFormTheme()</code> de nuestra clase de administración.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">/* Acme/DemoBundle/Admin/EntityAdmin.php */
<?php
namespace Allegro\SitesBundle\Admin;

...
use Allegro\SitesBundle\Form\DataTransformer\DateTimeToStringTransformer;
...

class EntityAdmin extends Admin
{
    /**
     * Adds a custom collection template.
     * @return array
     */
    public function getFormTheme()
    {
        return array_merge(
            parent::getFormTheme(),
            array('AcmeDemoBundle:Form:widgets.html.twig')
        );
    }</pre><p></p>
<p>Y con esto terminamos. Si bien la implementación no es tan rápida, no es compleja y es valiosa porque muestra una manera para personalizar cualquier campo en los administradores de nuestros bundles.</p>
<p>La entrada <a href="https://fugadigital.com/agregar-campos-informativos-en-formularios-de-sonata-admin/">Agregar campos informativos en formularios de Sonata Admin</a> se publicó primero en <a href="https://fugadigital.com">fuga_digital</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://fugadigital.com/agregar-campos-informativos-en-formularios-de-sonata-admin/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Distribuyendo una app de escritorio en Java</title>
		<link>https://fugadigital.com/distribuyendo-una-app-de-escritorio-en-java/</link>
					<comments>https://fugadigital.com/distribuyendo-una-app-de-escritorio-en-java/#respond</comments>
		
		<dc:creator><![CDATA[Arturo]]></dc:creator>
		<pubDate>Sat, 05 Apr 2014 14:33:09 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[ant]]></category>
		<category><![CDATA[app]]></category>
		<category><![CDATA[appbundler]]></category>
		<category><![CDATA[buid]]></category>
		<category><![CDATA[deploy]]></category>
		<category><![CDATA[desktop]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[launch4j]]></category>
		<category><![CDATA[multiplatform]]></category>
		<category><![CDATA[proguard]]></category>
		<guid isPermaLink="false">http://fugadigital.startupdigital.net/?p=9</guid>

					<description><![CDATA[<p>Java es un gran lenguaje, y como todos tiene ventajas y desventajas. Al momento de distribuir nuestra aplicación, se pueden realizar (y mejor aún, automatizar) algunas adecuaciones para ofrecer un paquete fácil de usar, pues una de las desventajas en Java es la falta de una integración adecuada para cada una de las plataformas soportadas. &#8230; </p>
<p class="link-more"><a href="https://fugadigital.com/distribuyendo-una-app-de-escritorio-en-java/" class="more-link">Continuar leyendo<span class="screen-reader-text"> "Distribuyendo una app de escritorio en Java"</span></a></p>
<p>La entrada <a href="https://fugadigital.com/distribuyendo-una-app-de-escritorio-en-java/">Distribuyendo una app de escritorio en Java</a> se publicó primero en <a href="https://fugadigital.com">fuga_digital</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Java es un gran lenguaje, y como todos tiene ventajas y desventajas. Al momento de distribuir nuestra aplicación, se pueden realizar (y mejor aún, automatizar) algunas adecuaciones para ofrecer un paquete fácil de usar, pues una de las desventajas en Java es la falta de una integración adecuada para cada una de las plataformas soportadas.</p>
<figure id="attachment_18" aria-describedby="caption-attachment-18" style="width: 300px" class="wp-caption aligncenter"><a href="https://fugadigital.com/wp-content/uploads/2014/04/brick-bicycle.jpg"><img decoding="async" src="https://fugadigital.com/wp-content/uploads/2014/04/brick-bicycle-300x214.jpg" alt="Impresión de algunos al recibir un JAR y sus librerías" width="300" height="214" class="size-medium wp-image-18" srcset="https://fugadigital.com/wp-content/uploads/2014/04/brick-bicycle-300x214.jpg 300w, https://fugadigital.com/wp-content/uploads/2014/04/brick-bicycle.jpg 638w" sizes="(max-width: 300px) 100vw, 300px" /></a><figcaption id="caption-attachment-18" class="wp-caption-text">Impresión de algunos al recibir un JAR y sus librerías</figcaption></figure>
<p>Siguiendo esta guía vamos a generar un proceso en Ant que construirá paquetes para Windows, OS X, Linux y multiplataforma. Los paquetes específicos para plataforma tendrán un ejecutable nativo, en tanto que el multiplataforma llevará el JAR optimizado. Los tres paquetes incluirán también archivos varios necesarios para la distribución como licencias o instructivos.</p>
<p>El JAR de la aplicación usado para las distribuciones será optimizado en tamaño, desempeño y código usando <a href="http://proguard.sourceforge.net/">Proguard</a>, que es una excelente herramienta para pulir un JAR a distribuir. Para generar el ejecutable de Windows se usará la herramienta <a href="http://launch4j.sourceforge.net/">Launch4j</a>. Para la app de OS X se usará <a href="https://java.net/projects/appbundler">jarbundler</a>.</p>
<p><span id="more-9"></span></p>
<p>Primero hay que crear un directorio para almacenar las herramientas a usar, un buen lugar puede ser dentro de la raiz del proyecto pero puedes ponerla donde mejor te ajuste. <a href="http://sourceforge.net/projects/proguard/files/">Descarga Proguard</a> y coloca el JAR (proguard-*.**.jar) en tu directorio de herramientas de construcción. Ahora <a href="http://sourceforge.net/projects/launch4j/files/">Descarga Launch4j</a> y descomprime el paquete en el mismo lugar donde hayas colocado Proguard. Finalmente <a href="https://java.net/projects/appbundler/downloads">desarcarga appblundler</a> y coloca el jar en el mismo directorio que las herramientas anteriores.</p>
<p>Si usas Linux x64 y en caso de que quieras hacer el ejecutable de Windows, es muy probable que debas agregar soporte para procesar librerías de 32 bits (por el binario <em>windres</em> incluido en la distribución de Linux). Si usas una distro basada en Debian puedes habilitar el uso de paquetes de 32 bits con las siguientes instrucciones:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">$ sudo dpkg --add-architecture i386
$ sudo aptitude update
$ sudo aptitude install ia32-libs</pre><p> </p>
<p><small>Notas: Esta guia la he implementado en #! 11 x64 y NetBeans 8 y posiblemente necesites hacer adaptaciones dependiendo de tu SO e IDE. Al ir pegando el código, revisa los valores de las variables o propiedades para ajustarlas a tus necesidades, algunos valores incluyen rutas que deberás actualizar.</small></p>
<p>Primero vamos a definir un nuevo archivo build que contendrá la automatización del proceso. Para esto necesitamos crear un nuevo archivo: <em>build-dist.xml</em>. Hazlo copiando el archivo <em>build.xml</em> en la misma ubicación (la raiz del proyecto).</p>
<p>Edita <em>build.xml</em> dejándolo con algo como:</p><pre class="urvanov-syntax-highlighter-plain-tag"><?xml version="1.0" encoding="UTF-8"?>

<project default="default" basedir=".">
    <description>Builds, tests, and runs the application.</description>
    <import file="nbproject/build-impl.xml"/>
    <import file="build-dist.xml" />

    <target name="-post-jar">
        <echo message="${line.separator}*** Run the Ant target 'dist-generate-pack' to generate ditribution packages${line.separator} " />
        <antcall target="dist-generate-pack"/>
    </target>
</project></pre><p>Nota la importación de <em>build-dist.xml</em> y el llamado <code>antcall</code> para controlar la ejecución del proceso.</p>
<p>Ahora edita <em>build-dist.xml</em> para dejarlo con la siguiente estructura inicial:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><?xml version="1.0" encoding="UTF-8"?>

<project default="default" basedir=".">
    <description>Generate distributable packages.</description>

    <!-- Import project properties -->
    <property file="nbproject/project.properties"/>
    <!-- Directory containing resources to add -->
    <property name="resources.dir" value="resources"/>
    <!-- Directory containing external tools -->
    <property name="build.ext.dir"     value="build-ext"/>
    <!-- Directory resources for  -->
    <property name="ext.resources.dir" value="${build.ext.dir}/resources"/>
    <property name="newline"           value="${line.separator}"/>

    <target name="dist-generate-pack">
        <property file="nbproject/project.properties"/>

        <echo message="${line.separator} - Distribution generation process will start...${line.separator}${line.separator}"/>

        <antcall target="dist-optimize-app"/>
        <!--<antcall target="dist-single-jar"/>-->
        <!--<antcall target="dist-wrap-win"/>-->
        <!--<antcall target="dist-wrap-osx"/>-->
        <!--<antcall target="dist-wrap-linux"/>-->
        <!--<antcall target="dist-pack-win"/>-->
        <!--<antcall target="dist-pack-osx"/>-->
        <!--<antcall target="dist-pack-linux"/>-->
        <!--<antcall target="dist-pack-multi"/>-->
        <!--<antcall target="dist-clean"/>-->
    </target>

</project></pre><p>Lo que haremos ahora será agregar cada bloque correspondiente a cada llamado <code>antcall</code>. Descomenta/comenta los llamados según vayas avanzando para hacer pruebas.</p>
<p>Lo primero será pulir nuestro JAR utilizando Proguard. Esta aplicación se encargará de remover código sin usar, optimizar a nivel bytecode nuestra aplicación y además obfuscar la estructura del fuente, todo esto nos viene genial.</p>
<p>Agrega el siguiente <code>target</code> a <em>build-dist.xml</em>:</p><pre class="urvanov-syntax-highlighter-plain-tag"><!-- Shrink, optimize and obfuscate application -->
    <target name="dist-optimize-app">
        <!-- Config -->
        <!-- JRE directory location -->
        <property name="ob.jre.dir"            value="/opt/java-oracle/jre"/>
        <!-- vm.jar for IBM's JVM, classes.jar for OS X -->
        <property name="ob.runtime.jar"        value="${ob.jre.dir}/lib/rt.jar"/>
        <!-- location of proguard relative to project root dir -->
        <property name="ob.proguard.jar"       value="${build.ext.dir}/proguard-4.11.jar"/>
        <!-- Javac classpath, defined in nbproject/project.properties -->
        <property name="ob.javac.cp"           value="${javac.classpath}"/>
        <!-- The jar app location, defined in nbproject/project.properties -->
        <property name="ob.input.jar"          value="${dist.jar}"/>
        <!-- New name (w/o ext) for input jar file -->
        <property name="ob.input.name"         value="${application.title}-orig"/>
        <!-- The resultant jar location -->
        <property name="ob.output.dir"         value="${dist.dir}"/>
        <!-- Name (w/o ext) for the resultant .jar & .map files  -->
        <property name="ob.output.name"        value="${application.title}"/>
        <!-- 5 optimization pases are enough -->
        <property name="ob.optimizations"      value="5"/>

        <!-- Routine -->
        <dirname property="ob.input.dir"       file="${ob.input.jar}"/>
        <property name="ob.input.mov.jar"      value="${ob.input.dir}/${ob.input.name}.jar"/>
        <move file="${ob.input.jar}"           tofile="${ob.input.mov.jar}"/>
        <property name="ob.output.jar"         value="${ob.output.dir}/${ob.output.name}.jar"/>
        <property name="ob.output.map"         value="${ob.output.dir}/${ob.output.name}.map"/>

        <echo message="${newline}** Shrinking, optimizing and obfuscating application..."/>
        <echo message="   Input jar:           ${ob.input.jar}"/>
        <echo message="   Input jar new name:  ${ob.input.mov.jar}"/>
        <echo message="   Output jar:          ${ob.output.jar}"/>
        <echo message="   Mapping file:        ${ob.output.map}"/>
        <mkdir dir="${ob.output.dir}"/>
        <taskdef resource="proguard/ant/task.properties" classpath="${ob.proguard.jar}"/>
        <proguard printmapping="${ob.output.map}"
                  overloadaggressively="true"
                  ignorewarnings="false"
                  optimize="true"
                  optimizationpasses="${ob.optimizations}"
                  obfuscate="true"
                  shrink="true"
                  verbose="false"
                  renamesourcefileattribute="SourceFile"
                  repackageclasses=""
                  printseeds="on">

            <libraryjar path="${ob.javac.cp}"/>
            <libraryjar file="${ob.runtime.jar}"/>
            <!-- for javax.crypto classes -->
            <libraryjar file="${ob.jre.dir}/lib/jce.jar"/>

            <injar  file="${ob.input.mov.jar}"/>
            <outjar file="${ob.output.jar}"/>


            <!-- ### Specific configuration ### -->
            <keep name="com.fugadigital.util.Money"/>
            <!-- I added some JDialog subclasses that I want to keep -->
            <keep access="public" extends="javax.swing.JDialog">
                <method access="public protected"/>
                <constructor access="public" parameters="java.awt.Frame"/>
            </keep>


            <!-- ### Standard application configuration ### -->
            <adaptclassstrings />
            <adaptresourcefilenames />

            <!-- Preserve all annotations -->
            <keepattribute name="*Annotation*"/>
            <keepattribute name="InnerClasses"/>
            <keepattribute name="Signature"/>
            <keepattribute name="Deprecated"/>
            <keepattribute name="EnclosingMethod"/>
            <keepattribute name="SourceFile"/>
            <keepattribute name="LineNumberTable"/>

            <keep implements="java.sql.Driver"/>
            <keep extends="ch.qos.logback.core.filter.Filter"/>

            <!-- Preserve main method -->
            <keep name="**${application.title}">
                <method access    ="public static"
                        type      ="void"
                        name      ="main"
                        parameters="java.lang.String[]"/>
            </keep>


            <keep extends="javax.swing.plaf.ComponentUI">
                <method access    ="public static"
                        type      ="javax.swing.plaf.ComponentUI"
                        name      ="createUI"
                        parameters="javax.swing.JComponent"/>
            </keep>

            <keep annotation="javax.persistence.*" type="class">
                <field />
                <method />
            </keep>

            <!-- Preserve all native method names and the names of their classes. -->
            <keepclasseswithmembernames>
                <method access="native"/>
            </keepclasseswithmembernames>

            <!-- Preserve the methods that are required in all enumeration classes. -->
            <keepclassmembers extends="java.lang.Enum">
                <method access="public static"
				type="**[]"
				name="values"
				parameters=""/>
                <method access="public static"
				type="**"
				name="valueOf"
				parameters="java.lang.String"/>
            </keepclassmembers>

            <!-- Explicitly preserve all serialization members. The Serializable
                 interface is only a marker interface. If code contains serializable
                 classes that have to be backward compatible, refer to the manual. -->
            <keepnames implements="java.io.Serializable"/>
            <keepclassmembers implements="java.io.Serializable">
                <field  access    ="static final"
                        type      ="long"
                        name      ="serialVersionUID"/>
                <field  access    ="static final"
                        type      ="java.io.ObjectStreamField[]"
                        name      ="serialPersistentFields"/>
                <method access    ="private"
                        type      ="void"
                        name      ="writeObject"
                        parameters="java.io.ObjectOutputStream"/>
                <method access    ="private"
                        type      ="void"
                        name      ="readObject"
                        parameters="java.io.ObjectInputStream"/>
                <method type      ="java.lang.Object"
                        name      ="writeReplace"
                        parameters=""/>
                <method type      ="java.lang.Object"
                        name      ="readResolve"
                        parameters=""/>
            </keepclassmembers>
        </proguard>
        <echo message="   DONE"/>
    </target></pre><p></p>
<p>Verifica que la configuración de las propiedades sea correcta y construye tu paquete. Después de compilar y con &#8216;algo de suerte&#8217; se generará un nuevo JAR con el nombre definido en la propiedad <code>ob.jar.name</code>, este JAR es la aplicación optimizada. Observa que he dejado en la sección de código configuraciones de ejemplo particulares a una aplicación. En caso de que tengas errores consulta la traza y agrega las configuraciones propias para tu aplicación. Consulta el <a href="http://proguard.sourceforge.net/#manual/usage.html">manual de uso</a> en el sitio de Proguard para esto.</p>
<p>El segundo paso es generar un solo JAR que contenga la aplicación procesada y las librerías. Apendiza el siguiente <code>target</code> en <em>build-dist.xml</em>:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><!--  Generate fat jar -->
    <target name="dist-single-jar">
        <!-- Config -->
        <!-- The library dir -->
        <property name="single.lib.dir"        value="${dist.dir}/lib"/>
        <!-- The application JAR -->
        <property name="single.input.jar"      value="${dist.jar}"/>
        <!-- New name (w/o ext) for input jar file -->
        <property name="single.input.name"     value="${application.title}-slim"/>
        <!-- The location of the directory for the unified JAR -->
        <property name="single.output.dir"     value="${dist.dir}"/>
        <!-- Name (w/o ext) for the resultant .jar file  -->
        <property name="single.output.name"    value="${application.title}"/>

        <!-- Routine -->
        <dirname property="single.input.dir"   file="${single.input.jar}"/>
        <property name="single.input.mov.jar"  value="${single.input.dir}/${single.input.name}.jar"/>
        <move file="${single.input.jar}"       tofile="${single.input.mov.jar}"/>
        <property name="single.output.jar"     value="${single.output.dir}/${single.output.name}.jar"/>

        <echo message="${newline}** Generating fat jar..."/>
        <echo message="   Input jar:           ${single.input.jar}"/>
        <echo message="   Input jar new name:  ${single.input.mov.jar}"/>
        <echo message="   lib dir:             ${single.lib.dir}"/>
        <echo message="   Output jar:          ${single.output.jar}"/>

        <jar jarfile="${single.output.jar}">
            <zipfileset src="${single.input.mov.jar}" excludes="META-INF/*"/>
            <zipgroupfileset dir="${single.lib.dir}" includes="*.jar" excludes="META-INF/*"/>
            <manifest>
                <attribute name="SplashScreen-Image" value="com/recipeman/resources/splashscreen.png"/>
                <attribute name="Main-Class" value="${main.class}"/>
            </manifest>
        </jar>
        <echo message="   DONE"/>
    </target></pre><p></p>
<p>Igualmente, verifica la configuración de las propiedades del <code>target</code> y compila. El proceso de construcción ahora generará un nuevo archivo que contendrá a nuestra aplicación y sus librerías en un solo .jar.</p>
<p>&nbsp;<br />
Desde este momento tenemos un JAR más facil de distribuir. La tarea ahora es ofrecer métodos para ejecutar nuestra aplicación de una manera más amigable, algo que conseguiremos creando lanzadores nativos para algunas plataformas.</p>
<p>Para Windows generaremos un .exe a través de Launch4j. Crea un archivo .xml dentro del directorio del Launch4j para establecer la configuración necesaria. El siguiente ejemplo generará un ejecutable que requirá una JVM 1.6.0+ instalada para funcionar, redireccionando a la página de descarga en español para instalar JAVA cuando no esté disponible, y con preferencia por una JVM de 64bits:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><?xml version="1.0" encoding="UTF-8"?>
<launch4jConfig>
  <dontWrapJar>false</dontWrapJar>
  <headerType>gui</headerType>
  <errTitle></errTitle>
  <cmdLine></cmdLine>
  <chdir></chdir>
  <priority>normal</priority>
  <downloadUrl>http://java.com/es/download</downloadUrl>
  <supportUrl></supportUrl>
  <stayAlive>false</stayAlive>
  <manifest></manifest>
  <icon>../resources/app_multi.ico</icon>
  <jre>
    <path></path>
    <bundledJre64Bit>false</bundledJre64Bit>
    <minVersion>1.6.0</minVersion>
    <maxVersion></maxVersion>
    <jdkPreference>preferJre</jdkPreference>
    <runtimeBits>64/32</runtimeBits>
  </jre>
</launch4jConfig></pre><p></p>
<p>Agrega el ícono que usarás para el .exe en el mismo directorio en que has puesto la configuración anterior. Consulta la <a href="http://launch4j.sourceforge.net/docs.html">documentación</a> para configuraciones más específicas.</p>
<p>Ahora, para ejecutar el proceso, agrega a <em>build-dist.xml</em> el siguiente <code>target</code>:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><!--  Generate the Windows wrapper -->
    <target name="dist-wrap-win">
        <!-- Config -->
        <!-- Jar to use -->
        <property name="wrap.input.jar"        value="${dist.jar}"/>
        <!-- The resultant files location -->
        <property name="wrap.output.dir"       value="${dist.dir}"/>
        <!-- -Name for the resultant executable file  -->
        <property name="wrap.output.exe"       value="${application.title}.exe"/>
        <!-- Application main class -->
        <property name="wrap.main.class"       value="${main.class}"/>
        <!-- Launch4j base directory -->
        <property name="wrap.launch4j.dir"     value="${build.ext.dir}/launch4j"/>

        <!-- Routine -->
        <echo message="${newline}** Generating wrapper..."/>
        <echo message="   Launch4j jar:        ${wrap.launch4j.dir}/launch4j.jar"/>
        <echo message="   Input jar:           ${wrap.input.jar}"/>
        <echo message="   Output file:         ${dist.dir}/${wrap.output.exe}"/>
        <taskdef name="launch4j"
                classname="net.sf.launch4j.ant.Launch4jTask"
                classpath="${wrap.launch4j.dir}/launch4j.jar
                        :${wrap.launch4j.dir}/lib/xstream.jar"/>

        <launch4j
                configfile="${wrap.launch4j.dir}/conf-nb.xml"
                jar="${wrap.input.jar}"
                outfile="${dist.dir}/${wrap.output.exe}"
        />
        <echo message="   DONE"/>
    </target></pre><p></p>
<p>&nbsp;<br />
Para generar la aplicación de OS X usaremos el siguiente target:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><!--  Generate the OS X wrapper -->
    <taskdef name="bundleapp"
             classname="com.oracle.appbundler.AppBundlerTask"   
             classpath="build-ext/appbundler-1.0.jar" />
    <target name="dist-wrap-osx">
        <!-- Config -->
        <!-- Jar to use -->
        <property name="wrap.input.jar"        value="${dist.jar}"/>
        <!-- The resultant files location -->
        <property name="wrap.output.dir"       value="${dist.dir}"/>
        <!-- Name for the resultant application directory  -->
        <property name="wrap.output.app"       value="${application.title}.app"/>
        <!-- Resources  -->
        <property name="wrap.icon.file"        value="GenericApp.icns"/>

        <!-- Routine -->
        <property name="app.dir" value="${dist.dir}/${wrap.output.app}"/>
        <echo message="${newline}** Generating wrapper..."/>
        <echo message="   Input jar:           ${wrap.input.jar}"/>
        <echo message="   Output file:         ${app.dir}"/>

        <bundleapp outputdirectory="dist"
            name="${application.title}"
            displayname="${application.title}"
            identifier="components.${application.title}"
            mainclassname="${main.class}">
            <classpath file="${wrap.input.jar}"/>
        </bundleapp>
        <delete file="${app.dir}/Contents/Resources/${wrap.icon.file}"/>
        <copy todir="${app.dir}/Contents/Resources">
          <fileset dir="${ext.resources.dir}" includes="${wrap.icon.file}"/>
        </copy>
    </target></pre><p></p>
<p>Para linux usaremos la estrategia de <a href="http://www.linuxjournal.com/content/add-binary-payload-your-shell-scripts">agregar payloads</a> a scripts, que apendizará un lanzador y el JAR en <a href="https://coderwall.com/p/ssuaxa">un solo archivo</a>. Crea el lanzador dentro del directorio de herramientas de construcción con el siguiente contenido:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">#!/bin/sh

MYSELF=`which "$0" 2>/dev/null`
[ $? -gt 0 -a -f "$0" ] && MYSELF="./$0"
java=java
if test -n "$JAVA_HOME"; then
    java="$JAVA_HOME/bin/java"
fi
exec "$java" $java_args -jar $MYSELF "$@"
exit 1</pre><p></p>
<p>Ahora, agrega a <em>build-dist.xml</em> un nuevo <code>tarjet</code> con la ejecución del cat que unirá el lanzador y el JAR:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><!--  Generate the GNU/Linux wrapper -->
    <target name="dist-wrap-linux">
        <!-- Config -->
        <!-- Jar to use -->
        <property name="wrap.input.jar"        value="${dist.jar}"/>
        <!-- launcher script location -->
        <property name="wrap.nixwrap.path"     value="${build.ext.dir}/jar2nix.sh"/>
        <!-- The resultant files location -->
        <property name="wrap.output.dir"       value="${dist.dir}"/>
        <!-- -Name for the resultant .run file  -->
        <property name="wrap.output.run"       value="${application.title}.run"/>

        <!-- Routine -->
        <echo message="${newline}** Generating wrapper..."/>
        <echo message="   Input jar:           ${wrap.input.jar}"/>
        <echo message="   Output file:         ${dist.dir}/${wrap.output.run}"/>
        <exec executable="sh" output="/dev/null">
            <arg value="-c"/>
            <arg value="cat ${wrap.nixwrap.path} ${wrap.input.jar} > ${dist.dir}/${wrap.output.run}"/>
        </exec>
        <exec executable="chmod">
            <arg value="+x"/>
            <arg value="${dist.dir}/${wrap.output.run}"/>
        </exec>

        <echo message="   DONE"/>
    </target></pre><p></p>
<p>(Si te preguntas por que usar <code>sh</code> como ejecutable en lugar de <code>cat</code> directamente, es porque la manera en que se envían los parametros impide la redirección y tanto el atributo <code>output</code> de <code>&lt;exec&gt;</code> como el uso del subelemento <code>&lt;redirector&gt;</code> modifican la salida generando archivos corruptos.)</p>
<p><a href="https://fugadigital.com/wp-content/uploads/2014/04/cat-package.jpg"><img loading="lazy" decoding="async" src="https://fugadigital.com/wp-content/uploads/2014/04/cat-package-247x300.jpg" alt="cat-package" width="247" height="300" class="aligncenter size-medium wp-image-19" srcset="https://fugadigital.com/wp-content/uploads/2014/04/cat-package-247x300.jpg 247w, https://fugadigital.com/wp-content/uploads/2014/04/cat-package.jpg 400w" sizes="auto, (max-width: 247px) 100vw, 247px" /></a></p>
<p>Para terminar vamos a agregar las instrucciones que generan los archivos de ditribución: un ZIP para Windows, un TAR.GZ para OS X, un TAR.BZ2 para Linux y un TAR.GZ multiplataforma.</p>
<p>Los tres <code>target</code> siguientes generarán archivos comprimidos que contendrán un directorio raiz, éste a su vez contendrá el wrapper o jar y un directorio con documentos varios.</p>
<p>Para Windows agrega el siguiente <code>target</code>:</p><pre class="urvanov-syntax-highlighter-plain-tag"><!--  Generate Windows zip file -->
    <target name="dist-pack-win">
        <!-- Config -->
        <!-- Location of the wrapper -->
        <property name="pack.wrap.dir"         value="${dist.dir}"/>
        <!-- Wrapper filename -->
        <property name="pack.wrap.file"        value="${application.title}.exe"/>
        <!-- Directory with resources to include -->
        <property name="pack.resources.dir"    value="${resources.dir}/app"/>
        <!-- Name for included resurces directoy -->
        <property name="pack.resources.name"    value="app"/>
        <!-- Location of the compressed file -->
        <property name="pack.output.dir"       value="${dist.dir}"/>
        <!-- compressed package filename -->
        <property name="pack.output.file"      value="${application.title}-win.zip"/>
        <!-- Directory name of the root compressed file contents -->
        <property name="pack.root.dir"         value="${application.title}"/>

        <!-- Routine -->
        <echo message="${newline}** Generating distribution..."/>
        <echo message="   Wrapper file:        ${pack.wrap.dir}/${pack.wrap.file}"/>
        <echo message="   Resources dir:       ${pack.resources.dir}"/>
        <echo message="   Internal root dir:   ${pack.root.dir}"/>

        <zip destfile="${pack.output.dir}/${pack.output.file}">
            <zipfileset
                prefix="${pack.root.dir}"
                dir="${pack.wrap.dir}"
                includes="${pack.wrap.file}"/>
            <zipfileset
                prefix="${pack.root.dir}/${pack.resources.name}"
                dir="${pack.resources.dir}"
                includes="**"/>
        </zip>
        <echo message="   DONE"/>
    </target></pre><p></p>
<p>Para OS X agrega el siguiente <code>target</code>:</p><pre class="urvanov-syntax-highlighter-plain-tag"><!--  Generate OS X tar.gz file -->
    <target name="dist-pack-osx">
        <!-- Config -->
        <!-- Location of the wrapper -->
        <property name="pack.wrap.dir"         value="${dist.dir}"/>
        <!-- Wrapper filename -->
        <property name="pack.wrap.file"        value="${application.title}.app"/>
        <!-- Directory with resources to include -->
        <property name="pack.resources.dir"    value="${resources.dir}/app"/>
        <!-- Name for included resurces directoy -->
        <property name="pack.resources.name"    value="app"/>
        <!-- Location of the compressed file -->
        <property name="pack.output.dir"       value="${dist.dir}"/>
        <!-- compressed package filename -->
        <property name="pack.output.file"      value="${application.title}-osx.tar.gz"/>
        <!-- Directory name of the root compressed file contents -->
        <property name="pack.root.dir"         value="${application.title}"/>

        <!-- Routine -->
        <echo message="${newline}** Generating distribution..."/>
        <echo message="   Wrapper file:        ${pack.wrap.dir}/${pack.wrap.file}"/>
        <echo message="   Resources dir:       ${pack.resources.dir}"/>
        <echo message="   Internal root dir:   ${pack.root.dir}"/>

        <tar destfile="${pack.output.dir}/${pack.output.file}" compression="gzip">
            <tarfileset
                prefix="${pack.root.dir}"
                dir="${pack.wrap.dir}"
                excludes="**/JavaAppLauncher"
                includes="${pack.wrap.file}/**"/>
            <tarfileset
                prefix="${pack.root.dir}/${pack.wrap.file}/Contents/MacOS"
                dir="${pack.wrap.dir}/${pack.wrap.file}/Contents/MacOS"
                mode="755"
                includes="JavaAppLauncher"/>
            <tarfileset
                prefix="${pack.root.dir}/${pack.resources.name}"
                dir="${pack.resources.dir}"
                includes="**"/>
        </tar>
        <echo message="   DONE"/>
    </target></pre><p></p>
<p>Para Linux agrega el siguiente <code>target</code>:</p><pre class="urvanov-syntax-highlighter-plain-tag"><!--  Generate Linux tar.bz2 file -->
    <target name="dist-pack-linux">
        <!-- Config -->
        <!-- Location of the wrapper -->
        <property name="pack.wrap.dir"         value="${dist.dir}"/>
        <!-- Wrapper filename -->
        <property name="pack.wrap.file"        value="${application.title}.run"/>
        <!-- Directory with resources to include -->
        <property name="pack.resources.dir"    value="${resources.dir}/app"/>
        <!-- Name for included resurces directoy -->
        <property name="pack.resources.name"    value="app"/>
        <!-- Location of the compressed file -->
        <property name="pack.output.dir"       value="${dist.dir}"/>
        <!-- compressed package filename -->
        <property name="pack.output.file"      value="${application.title}-linux.tar.bz2"/>
        <!-- Directory name of the root compressed file contents -->
        <property name="pack.root.dir"         value="${application.title}"/>

        <!-- Routine -->
        <echo message="${newline}** Generating distribution..."/>
        <echo message="   Wrapper file:        ${pack.wrap.dir}/${pack.wrap.file}"/>
        <echo message="   Resources dir:       ${pack.resources.dir}"/>
        <echo message="   Internal root dir:   ${pack.root.dir}"/>

        <tar destfile="${pack.output.dir}/${pack.output.file}" compression="bzip2">
            <tarfileset
                prefix="${pack.root.dir}"
                dir="${pack.wrap.dir}"
                includes="${pack.wrap.file}"
                mode="755"/>
            <tarfileset
                prefix="${pack.root.dir}/${pack.resources.name}"
                dir="${pack.resources.dir}"
                includes="**"/>
        </tar>
        <echo message="   DONE"/>
    </target></pre><p></p>
<p>Para la distribución multiplataforma agrega el siguiente <code>target</code>:</p><pre class="urvanov-syntax-highlighter-plain-tag"><!--  Generate multiplatform tar.gz file -->
    <target name="dist-pack-multi">
        <!-- Config -->
        <!-- Location of the jar -->
        <property name="pack.jar.dir"         value="${dist.dir}"/>
        <!-- Wrapper filename -->
        <property name="pack.jar.file"        value="${application.title}.jar"/>
        <!-- Directory with resources to include -->
        <property name="pack.resources.dir"    value="${resources.dir}/app_multi"/>
        <!-- Name for included resurces directoy -->
        <property name="pack.resources.name"    value="app"/>
        <!-- Location of the compressed file -->
        <property name="pack.output.dir"       value="${dist.dir}"/>
        <!-- compressed package filename -->
        <property name="pack.output.file"      value="${application.title}-multi.tar.gz"/>
        <!-- Directory name of the root compressed file contents -->
        <property name="pack.root.dir"         value="${application.title}"/>

        <!-- Routine -->
        <echo message="${newline}** Generating distribution..."/>
        <echo message="   Wrapper file:        ${pack.jar.dir}/${pack.jar.file}"/>
        <echo message="   Resources dir:       ${pack.resources.dir}"/>
        <echo message="   Internal root dir:   ${pack.root.dir}"/>

        <tar destfile="${pack.output.dir}/${pack.output.file}" compression="gzip">
            <tarfileset
                prefix="${pack.root.dir}"
                dir="${pack.jar.dir}"
                includes="${pack.jar.file}"/>
            <tarfileset
                prefix="${pack.root.dir}"
                dir="${resources.dir}"
                includes="run.*"
                mode="755"/>
            <tarfileset
                prefix="${pack.root.dir}/${pack.resources.name}"
                dir="${pack.resources.dir}"
                includes="**"/>
        </tar>
        <echo message="   DONE"/>
    </target></pre><p>En este <code>target</code> además estoy agregando archivos <em>run.*</em> que son lanzadores comunes para ejecutar un JAR en diferentes plataformas.</p>
<p>Este último paso es realmente innecesario y elimina todos los archivos y directorios excepto nuestros paquetes distribuibles:</p><pre class="urvanov-syntax-highlighter-plain-tag"><!-- Delete leftovers -->
    <target name="dist-clean">
        <property file="nbproject/project.properties"/>
        <echo message="${newline}** Deleting leftovers..."/>
        <delete includeemptydirs="true">
            <fileset dir="${dist.dir}"
                excludes="**/${application.title}-win.zip,
                            **/${application.title}-osx.tar.gz,
                            **/${application.title}-linux.tar.bz2,
                            **/${application.title}-multi.tar.gz"
            />
        </delete>

        <echo message="   DONE"/>
    </target></pre><p></p>
<p>El archivo de construcción seguro es ahora considerablemente más largo pero muy versatil y adaptable. Aun cuando hay muchas mejoras posibles, esta es una buena base, así que agrega los cambios propios para tu proyecto y facilita la vida a tus usuarios (y la tuya por supuesto).</p>
<p>La entrada <a href="https://fugadigital.com/distribuyendo-una-app-de-escritorio-en-java/">Distribuyendo una app de escritorio en Java</a> se publicó primero en <a href="https://fugadigital.com">fuga_digital</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://fugadigital.com/distribuyendo-una-app-de-escritorio-en-java/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Escritorio Terminator en OpenBox</title>
		<link>https://fugadigital.com/escritorio-terminator-en-openbox/</link>
					<comments>https://fugadigital.com/escritorio-terminator-en-openbox/#respond</comments>
		
		<dc:creator><![CDATA[Arturo]]></dc:creator>
		<pubDate>Fri, 21 Mar 2014 21:13:34 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[crunchbang]]></category>
		<category><![CDATA[debian]]></category>
		<category><![CDATA[desktop]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[openbox]]></category>
		<category><![CDATA[script]]></category>
		<category><![CDATA[terminator]]></category>
		<category><![CDATA[wmctrl]]></category>
		<category><![CDATA[xdotool]]></category>
		<guid isPermaLink="false">http://fugadigital.startupdigital.net/?p=7</guid>

					<description><![CDATA[<p>Hace algunos días cambié de distribución a !#. Aprovechando que la distro no tiene DE y que mi escritorio siempre terminaba con basura decidí poner ahí algo más productivo y ligeramente geek: un Terminator. Con algo de información en los foros de la distro e insipiración he obtenido esto: &#160; La siguiente guía explica como &#8230; </p>
<p class="link-more"><a href="https://fugadigital.com/escritorio-terminator-en-openbox/" class="more-link">Continuar leyendo<span class="screen-reader-text"> "Escritorio Terminator en OpenBox"</span></a></p>
<p>La entrada <a href="https://fugadigital.com/escritorio-terminator-en-openbox/">Escritorio Terminator en OpenBox</a> se publicó primero en <a href="https://fugadigital.com">fuga_digital</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Hace algunos días cambié de distribución a !#. Aprovechando que la distro no tiene DE y que mi escritorio siempre terminaba con basura decidí poner ahí algo más productivo y ligeramente geek: un Terminator. Con algo de información en los foros de la distro e insipiración he obtenido esto:</p>
<figure id="attachment_20" aria-describedby="caption-attachment-20" style="width: 474px" class="wp-caption aligncenter"><a href="https://fugadigital.com/wp-content/uploads/2014/04/determinator-screenshot.jpg"><img loading="lazy" decoding="async" src="https://fugadigital.com/wp-content/uploads/2014/04/determinator-screenshot-1024x575.jpg" alt="Escritorio tres veces más productivo (y sexy)" class="size-large wp-image-20" width="474" height="266" srcset="https://fugadigital.com/wp-content/uploads/2014/04/determinator-screenshot-1024x575.jpg 1024w, https://fugadigital.com/wp-content/uploads/2014/04/determinator-screenshot-300x168.jpg 300w, https://fugadigital.com/wp-content/uploads/2014/04/determinator-screenshot.jpg 1366w" sizes="auto, (max-width: 474px) 100vw, 474px" /></a><figcaption id="caption-attachment-20" class="wp-caption-text">Escritorio tres veces más productivo (y sexy)</figcaption></figure>
<p>&nbsp;<br />
La siguiente guía explica como conseguir el Terminator de escritorio en OpenBox, aunque seguramente se puede hacer en otros gestores de ventanas sin mucho problema. La primera parte es definir un perfil específico para el Terminator del escritorio y agregar procedimientos para mostrarlo y ocultarlo. Esto es independiente del gestor de ventanas.</p>
<p>El siguiente es un ejemplo de configuración con un perfil para el escritorio:</p><pre class="urvanov-syntax-highlighter-plain-tag">[global_config]
  title_inactive_bg_color = "#000000"
  title_transmit_bg_color = "#000000"
  borderless = True
  handle_size = 0
  focus = system
[keybindings]
[profiles]
  [[default]]
    scrollbar_position = hidden
    palette = "#000000:#cc0000:#4e9a06:#c4a000:#3465a4:#75507b:#06989a:#d3d7cf:#555753:#ef2929:#8ae234:#fce94f:#729fcf:#ad7fa8:#34e2e2:#eeeeec"
    background_darkness = 0.8
    scrollback_lines = 5000
    background_type = transparent
    use_system_font = False
    cursor_color = "#d8d8d8"
    foreground_color = "#d8d8d8"
    color_scheme = custom
    font = Monospace 10
    background_color = "#2e3436"
    scrollback_infinite = True
  [[desktop]]
    scrollbar_position = hidden
    palette = "#000000:#cc0000:#4e9a06:#c4a000:#3465a4:#75507b:#06989a:#d3d7cf:#555753:#ef2929:#8ae234:#fce94f:#729fcf:#ad7fa8:#34e2e2:#eeeeec"
    scrollback_lines = 5000
    background_darkness = 0.0
    background_type = transparent
    use_system_font = False
    cursor_color = "#d8d8d8"
    foreground_color = "#d8d8d8"
    show_titlebar = False
    color_scheme = custom
    font = DejaVu Sans Mono 10
    scrollback_infinite = True
[layouts]
  [[default]]
    [[[child1]]]
      type = Terminal
      parent = window0
      profile = default
    [[[window0]]]
      type = Window
      parent = ""
[plugins]</pre><p><span id="more-7"></span></p>
<p>Para simular la función de mostrar escritorio/ventanas (Spr+D) hice un script que puede realizar la tarea usando alguna de las siguientes estrategias:</p>
<p>1) Guardar una referencia a la ventana activa, mostrar escritorio, y enfocar el Terminator de escritorio. Muy responsivo pero al traer las ventanas de nuevo, sólo traerá la que estaba activa.<br />
2) Guardar referencias de todas las ventanas no minimizadas, mostrar escritorio y enfocar. Algo menos responsivo, puede traer de vuelta todas las ventanas guardadas y enfocar la que estaba activa pero el orden de las ventanas se pierde.<br />
3) Mininizar una a una las ventanas para almacenar las referencias ordenadas y enfocar el Terminator. Menos responsivo al mostrar escritorio/ventanas pero conserva el orden de las ventanas.</p>
<p>La ubicación del script <em>showTDesktop</em> la he puesto en <em>/usr/bin</em>.</p><pre class="urvanov-syntax-highlighter-plain-tag">#!/bin/bash

termWinName='TerminatorAsDesktop'
strategy=3

isRunning=$(wmctrl -l -x | grep $termWinName)

# execute terminator for desktop
if [ "$isRunning" = "" ]
then
    wmctrl -k on
    $(terminator --profile=desktop --borderless --geometry=1120x0 -T$termWinName)

else
    # Ids in decimal
    curWinId=$(xdotool getactivewindow)
    termWinId=$(xdotool search --name $termWinName | head -1)

    ###     show desktop    ###
    if [ "$curWinId" != "$termWinId" ]
    then

        if [ $strategy = 1 ]
        then
            echo $curWinId &gt; /tmp/$termWinName
            wmctrl -k on


        elif [ $strategy = 2 ]
        then
            host=$(hostname)
            curWS=$(xdotool get_desktop)

            # cur win id in hex
            curWinIdX=$(printf "0x%0*x" 8 $curWinId)
            echo $curWinIdX &gt; /tmp/$termWinName

            while read aWindow;
            do
                winInWS=`expr match "$aWindow" "^..........  *$curWS $host "`
                if [ $winInWS != "0" ]
                then
                    #winTitle=${aWindow:$winInWS}
                    # win id in hex
                    winId=${aWindow:0:10}

                    winState=$(xwininfo -id $winId | grep 'Map State:')
                    winState=${winState:13}

                    # window not minimized
                    if [ "$winState" != "IsUnMapped" ]
                    then
                        #echo "$winId $winState $winTitle"
                        if [ "$curWinIdX" != "$winId" ]
                        then
                            echo $winId &gt;&gt; /tmp/$termWinName
                        #else
                        #    echo "main win already saved"
                        fi
                    fi
                fi
            done &lt; &lt;( wmctrl -l )
            wmctrl -k on


        elif [ $strategy = 3 ]
        then
            cat /dev/null &gt; /tmp/$termWinName
            curWin=$curWinId
            while [ "$curWin" != "" ]
            do
                echo $curWin &gt;&gt; /tmp/$termWinName
                xdotool windowminimize $curWin
                curWin="" ;
                curWin=$(xdotool getactivewindow) ;
            done
        fi

        wmctrl -a $termWinName


    ###    Restore windows    ###
    else
        if [ $strategy = 1 ]
        then
            lastWinId=$(cat /tmp/$termWinName)
            if [ $lastWinId = "" ]
            then
                lastWinId=$termWinId
            fi
            xdotool windowmap $lastWinId

        elif [ $strategy = 2 ] || [ $strategy = 3 ];
            then
            for winId in $(sed '1!G;h;$!d' /tmp/$termWinName)
            do
                xdotool windowmap $winId
            done
        fi
    fi
fi

exit 0</pre><p>&nbsp;<br />
La segunda parte es la integración en OpenBox.</p>
<p>Dentro de <em>~/.config/openbox/rc.xml</em> se definen el juego de teclas para llamar el script y las reglas para el despliegue del Terminator.</p>
<p>Activación de juego de teclas:</p><pre class="urvanov-syntax-highlighter-plain-tag"><keybind key="W-d">
      <action name="Execute">
        <startupnotify>
          <enabled>true</enabled>
          <name>Determinator</name>
        </startupnotify>
        <command>showTDesktop</command>
      </action>
    </keybind></pre><p>Despliegue de la ventana de Terminator para escritorio:</p><pre class="urvanov-syntax-highlighter-plain-tag"><application class="Terminator" name="terminator" title="TerminatorAsDesktop">
      <focus>yes</focus>
      <position force="no">
         <x>left</x>
         <y>0</y>
      </position>
      <layer>below</layer>
      <desktop>All</desktop>
      <skip_taskbar>yes</skip_taskbar>
      <skip_pager>yes</skip_pager>
      <maximized>vertical</maximized>
      <shade>no</shade>
      <iconic>no</iconic>
    </application></pre><p>Para terminar sólo falta quitar los bordes de las ventanas y remover la sombra del Terminator para hacer que no parezca una aplicación en ventana y reiniciar OpenBox y Compositor.</p>
<p>Los bordes de las ventanas se remueven editando el <em>themerc</em> del tema activo, modificando la siguiente propiedad:</p><pre class="urvanov-syntax-highlighter-plain-tag">border.width: 0</pre><p>La sombra se desactiva editando <em>~/.config/compton.conf</em>, modificando la siguiente propiedad:</p><pre class="urvanov-syntax-highlighter-plain-tag">shadow-exclude = [ ..., &quot;n:e:TerminatorAsDesktop&quot; ];</pre><p>Y es todo. Ahora puedes ver tus fondos de pantalla en todo su explendor mientras haces commit.</p>
<p>La entrada <a href="https://fugadigital.com/escritorio-terminator-en-openbox/">Escritorio Terminator en OpenBox</a> se publicó primero en <a href="https://fugadigital.com">fuga_digital</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://fugadigital.com/escritorio-terminator-en-openbox/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
