Menú

Mostrar Mensajes

Esta sección te permite ver todos los mensajes escritos por este usuario. Ten en cuenta que sólo puedes ver los mensajes escritos en zonas a las que tienes acceso en este momento.

Mostrar Mensajes Menú

Mensajes - Eleкtro

#4351
Terminar la ejecución de la aplicación de forma anómala usando el keyword End es algo que se debe evitar a toda costa, de todas formas en el handler donde indicas esa orden es normal que no te funcione como esperas.

Lo que deberías hacer es lo que ha comentado @El_Benjo al final de su comentario. Puedes utilizar un BackgroundWorker o mejor una Task con un token de cancelación en el cual realizarías una petición de cancelación al cerrar el form.

Aparte de eso, deberías reemplazar el uso de TimeSpan por un StopWatch, ya que la medida es más precisa y su utilización más simple.

Saludos
#4352
Foro Libre / Re: Teoria del Solipsismo
18 Octubre 2015, 09:39 AM
Me encantan los videos de Hypnos, siempre me hacen reflexionar sobre los enigmas de la vida y de la psique humana ...más de lo que ya lo suelo hacer.

Hay algo sobre lo que no se ha hablado en el video pero yo creo que cabe dentro de esta teoría, el dolor.

Cuando el ser humano se hace una herida considerablemente "grave" en una zona que el "cuerpo" o la "mente" o nuestra "biología" o lo que sea que lo reconoce como una parte vital para nuestra supervivencia, por ejemplo los ojos, automaticamente se activa una alarma en nuestro sistema que nuestro cerebro interpreta como "dolor" para avisarnos de que algo va muy mal, sin embargo, si nos hacemos el mismo tipo de herida en una zona "poco importante" para nuestra supervivencia, como por ejemplo una nalga, nuestro sistema no activará estas señales de forma tan "aguda", sentiremos menos dolor, o incluso nada.

Podemos comparar este ejemplo con el típico ejemplo de que para saber si estás soñando o estás en la "realidad" es suficiente con que alguien te pellizque en el brazo para saber si sientes dolor.

Lo que intento decir es que si te sacan un ojo te va a doler horrores, ¿pero por que se tiene que manifestar un síntoma de dolor si o si?, ¿a que debemos atribuir este comportamiento?, ¿a la realidad, a la evolución?, si ni siquiera comprendemos el sinificado de ninguno de esos conceptos.

Para mi todo esto es un misterio que obviamente nadie va a poder resolver en este siglo, pero es interesante pensar en ello para comprender que ni tan siquiera somos dueños de nosotros mismos, de nuestro cuerpo. El libre albedrio no existe a ese nivel de la arquitectura biológica, somos como un software que está basado o limitado por las reglas del hardware, o como una abstracción de código siendo el cerebro un nivel mayor de un concepto low-level, ya que no podemos decidir lo que nuestro cuerpo debe manifestar a "bajo nivel" como por ejemplo el dolor. ¿Por qué no podemos controlar esos aspectos de nosotros mismos?.

Yo lo único que se es que todo parece estar orquestado en la vida, en nuestras vidas, en cualquier tipo de vida. Todo está "programado" de alguna manera para que las cosas sucedan como tienen que suceder, ahora, ¿es esto lo que conocemos como realidad?.

Saludos
#4353
Cita de: OscarCadenas_91 en 17 Octubre 2015, 09:36 AMEstoy creando un aplicacion que busca palabras en un archivo de texto

El problema viene cuando intento usar archivo un poco mas grande la aplicacion se queda congelada, sin poder hacer nada.

Para solucionarlo simplemmente ejecuta las órdenes de manera asíncrona en el mismo thread de la UI, o en un thread separado.

Este ejemplo toma un archivo de texto de 300 MB, busca 3 palabras y muestra la suma de coincidencias de las palabras encontradas.



Código (vbnet) [Seleccionar]
Imports System.Threading.Tasks

Public NotInheritable Class Form1 : Inherits Form

   Private Sub Button1_Click(sender As Object, e As EventArgs) _
   Handles Button1.Click

       Dim ocurrences As Integer = -1

       Task.Factory.StartNew(Sub() Me.Find("C:\resumen.txt", {"Solucion", "Libro", "Autor"}, ocurrences)).
                ContinueWith(Sub()
                                 Me.Invoke(Sub()
                                               TextBox1.Text = String.Format("Coincidencias: {0}", CStr(ocurrences))
                                               Label1.Text = "Operación completada!"
                                           End Sub)
                             End Sub)

       Dim sw As New Stopwatch
       sw.Start()
       Do Until (ocurrences <> -1)
           Label1.Text = String.Format("{0} segundos...", sw.Elapsed.TotalSeconds.ToString("n2"))
           Application.DoEvents()
       Loop
       sw.Reset()

   End Sub

   Private Sub Find(ByVal textFilepath As String,
                    ByVal values As IEnumerable(Of String),
                    ByRef var As Integer)

       Using sr As New StreamReader(textFilepath, True)

           var = (From word As String In (sr.ReadToEnd.Split).AsParallel
                  Select (From value As String In values
                          Where word.Equals(value, StringComparison.OrdinalIgnoreCase)).Count
                 ).Sum

       End Using

   End Sub

End Class


El contador de segundos lo he colocado solo para que denotes que no se congela la UI, pero en realidad eso vuelve (algo)más lento el procedimiento xD.

He usado LINQ por su intuitiva y simplificada sintaxis, pero si quieres mayor optimización en respecto al rendimiento entonces usa un For. Aquí tienes varios ejemplos en C#:

C# .Net: Fastest Way to check if a string occurs within a string




Cita de: OscarCadenas_91 en 17 Octubre 2015, 09:36 AMBackgroundworker y Threads
Me podrian decir cual es la diferencia entre ambos



_____________________________________________________________________
EDITO: Ups, he ido a revisar mi comentario y no se por qué leí que preguntabas sobre "Tasks" en lugar de "Threads", me he dado cuenta tarde así que todo esto que comento aquí abajo es sobre la class Task y no sobre theads o hilos en general, pero de todas formas es lo que deberías utilizar, una Task.

Sobre las diferencias, la class Threading.Thread es el concepto o implementación low-level de un hilo, mientras que la class Threading.Thread sería un nivel superior de su implementación, ya que tiene mayor abstracción.

La class Threading.Task representa una operación asíncrona que es llevada acabo mediente hilos, los cuales son administrados mediante su propio planificador de hilos o thread scheduler.

No te conviene usar hilos tradicionales mediante Threading.Thread ya que está pensado para otros escenarios donde se requiere un mayor requisito de computación, es preferible que uses la class Threading.Task.
_____________________________________________________________________




La diferencia más grande tal vez está en su modo de empleo, ya que la class Task es la evolución de la class BackgroundWorker.

Existen muchas diferencias internas entre si en el contexto de como administra los hilos, su cancelación, la sincronia de código, etc. Para destacar todo ello habría que escribir un libro, así que si quieres aprender en profundidad sobre sus diferencias es mejor que busques en Google artículos que hablen sobre ello, pero en resumen el BackgroundWorker o BGW tiene varios problemas de diseño, y de por si es muy tedioso utilizarlo al estar basado u orientado en eventos; también tiene problemas de sincronización y de tokens de cancelación. Con el paso de los años es posible que Microsoft marque "deprecada" dicha class.

Te muestro una simple comparación de uso:

BGW:
Código (vbnet) [Seleccionar]
Dim WithEvents bgw As New BackgroundWorker

Private Sub Bgw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) _
Handles bgw.DoWork

   Thread.Sleep(1000)

End Sub

Private Sub Bgw_RunWorkerCompleted(ByVal sender As Object,ByVal e As RunWorkerCompletedEventArgs) _
Handles bgw.RunWorkerCompleted

   MessageBox.Show("Operation completed!")

End Sub


Private Sub RunBgw()

   Me.bgw.RunWorkerAsync()

End Sub


BGW simplificado:
Código (vbnet) [Seleccionar]
Sub ...

Using bgw As New BackgroundWorker

   AddHandler bgw.DoWork, Sub()
                              Thread.Sleep(1000)
                          End Sub

   AddHandler bgw.RunWorkerCompleted, Sub()
                                          MessageBox.Show("Operation completed!")
                                      End Sub
   bgw.RunWorkerAsync()

End Using

End sub


Task.Run:
Código (vbnet) [Seleccionar]
Async Sub ...

   Await Task.Run(Sub()
                      Thread.Sleep(1000)
                  End Sub)

   MessageBox.Show("Operation completed!")

End sub


Nota: Async/Await forman parte de .NetFx 4.5

Saludos!
#4354
Cita de: nisteeklod en 16 Octubre 2015, 15:09 PM...y siguen fijados.

Los hilos fijados suelen contener información importante sobre el aprendizaje de "X", o índices/recopilaciones que enlazan otros temas destacados del foro, manuales, y etcétera.

Es cierto que bastantes de esos hilos fijados contienen artículos cuya información se ha ido quedando bastante obsoleta, pero a falta de artículos más actuales y de calidad similar no creo que se pueda hacer nada en respecto a tareas de "limpieza", a menos que haya el suficiente tiempo para que algún interesado se proponga actualizar su contenido.

Saludos!
#4355
Cita de: #!drvy en 16 Octubre 2015, 05:56 AMObviamente mas dañino siempre sera el cigarrillo tradicional..

Eso podría ser discutible.  :P

Según vi hace tiempo en varios documentales, el cigarrillo electrónico lleva una especie de "tubos" en su interior que están compuestos por un material o sustancia muy tóxica hasta el punto de ser mortal si se ingiere o se inhala, no estoy hablando de las sustancias que se venden por separado que el consumidor puede comprar para cambiar el "sabor", sino de los materiales empleados en la fabricación del chisme (no recuerdo el nombre del material o sustancia, lo siento). En ocasiones el defecto es percetible por que el aparato deja de funcionar o huele a quemado y esas cosas, pero en otras circunstancias no se detecta a tiempo, el humo tóxico es inhalado y provoca adversiones bastante graves.

Por ende, el cigarrillo electrónico es mucho más dañino que el cigarrillo tradicional si se mira desde este punto de vista, ya que con un cigarrillo electrónico defectuoso de fábrica o que se vuelva defectuoso con el uso, el consumidor puede inhalar en dos días elementos con una toxicidad elevada que le provocarían los mismos efectos negativos comparados con 10 años inhalando humo tradicional (es solo un decir, una estimación personal nada científica);
en cambio, los únicos inconvenientes negativos adicionales que se le puede atribuir a un cigarrillo tradicional defectuoso de fábrica es que le falte la boquilla, o que el cigarro sea más corto, o ese tipo de cosas sin importancia, se entiende.

En el documental que vi ...que ya hace algunos años, trataba sobre la fabricación de los cigarrillos electrónicos, de su impacto en los jóvenes, y de la exportación de cigarrillos electrónicos chinos a España y el efecto que causó a mucha gente por que hubieron muchos defectuosos (y fueron problemas de salud más dañinos que el tabaco tradicional), y la moraleja de todo esto es que si se compran un cigarrillo electrónico asegúrense que no lo compran en un chino de todo a 1€, por que es peligroso comprarlo de mala calidad, ni tampoco de una marca china que sea respetable... por si acaso.

Saludos!
#4356
.NET (C#, VB.NET, ASP) / Re: habilitar menustrip
16 Octubre 2015, 06:17 AM
Cita de: d91 en 16 Octubre 2015, 00:13 AMen un panel cargo los usercontrol

No es necesario que heredes la trivialidad de un UserControl, puedes heredar un Form, solamente tienes que tener en cuenta desactivar la propiedad Form.TopLevel para hacer el MDI.

Form.TopLevel Property - MSDN




Cita de: d91 en 16 Octubre 2015, 00:13 AMquiero forzar al usuario a que cierre el usercontrol para poder abrir otro
pensé en deshabilitar el menú al abrir el usercontrol, pero no se en que evento habilitarlo para cuando se cierre el usercontrol

Te daré varias opciones...



1. - Si quieres desactivar/activar el menu entero, puedes suscribirte a los eventos Panel.ControlAdded y Panel.ControlRemoved

Ejemplo en VB.Net:
Código (vbnet) [Seleccionar]
Private Sub Panel3_ControlAdded(ByVal sender As Object, ByVal e As ControlEventArgs) _
Handles Panel3.ControlAdded,
       Panel3.ControlRemoved

   Me.nuevoClienteToolStripMenuItem.Enabled =
       Not DirectCast(sender, Panel).Controls.OfType(Of CustomMDIForm).Any

End Sub


Ejemplo en C#:
Código (csharp) [Seleccionar]
private void Panel3_ControlAdded_And_ControlRemoved(object sender, ControlEventArgs e) {
this.nuevoClienteToolStripMenuItem.Enabled = !((Panel)sender).Controls.OfType<CustomMDIForm>.Any();
}




2. - Si quieres desactivar/activar un item en particular del menu, puedes suscribirte al evento ToolStripMenuItem.DropDownOpening y acceder al item en cuestión en la colección ToolStripMenuItem.DropDownItems.

Ejemplo en VB.Net:
Código (vbnet) [Seleccionar]
Private Sub NuevoClienteToolStripMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) _
Handles nuevoClienteToolStripMenuItem.DropDownOpening

   DirectCast(sender, ToolStripMenuItem).DropDownItems(index:=0).Enabled =
       Not Me.Panel3.Controls.OfType(Of CustomMDIForm).Any

End Sub


Ejemplo en C#:
Código (csharp) [Seleccionar]
private void NuevoClienteToolStripMenuItem_Click(object sender, EventArgs e) {
((ToolStripMenuItem)sender).DropDownItems(index: 0).Enabled = !this.Panel3.Controls.OfType<CustomMDIForm>.Any();
}




3. - También puedes administrar la visibilidad del menú desde tu Form heredado o desde un Usercontrol, de la misma manera.

En tu herencia, le pasas la instancia del ToolStipMenuItem al Constructor de la class, y cambias su visibilidad suplantando los métodos OnLoad y OnDispose.

Ejemplo en VB.Net:
Código (vbnet) [Seleccionar]
Public NotInheritable Class CustomMDIForm : Inherits Form

   Public ReadOnly MenuItem As ToolStripMenuItem

   Public Sub New(ByVal parent As Control, ByVal menuItem As ToolStripMenuItem)

       With Me
           .TopLevel = False
           .Parent = parent
           .StartPosition = FormStartPosition.CenterParent
           .FormBorderStyle = FormBorderStyle.Sizable
           .Dock = DockStyle.Fill
       End With

       Me.MenuItem = menuItem

   End Sub

   Private Sub New()
   End Sub

   Protected Overrides Sub OnLoad(ByVal e As EventArgs)
       If (Me.MenuItem IsNot Nothing) Then
           Me.MenuItem.Enabled = False
       End If
       MyBase.OnLoad(e)
   End Sub

   Protected Overrides Sub Dispose(ByVal disposing As Boolean)
       If (Me.MenuItem IsNot Nothing) Then
           Me.MenuItem.Enabled = True
       End If
       MyBase.Dispose(Disposing)
   End Sub

End Class

+
Código (vbnet) [Seleccionar]
Public NotInheritable Class Form1 : Inherits Form

   Private cf As CustomMDIForm

   Public Sub New()
       Me.InitializeComponent()
       Me.cf = New CustomMDIForm(parent:=Me.Panel3, menuItem:=Me.nuevoClienteToolStripMenuItem)
   End Sub

   Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) _
   Handles MyBase.Load

       Me.cf.Show()

   End Sub

End Class


Traducción online a C#:
Código (csharp) [Seleccionar]
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;

public sealed class CustomMDIForm : Form {

public readonly ToolStripMenuItem MenuItem;

public CustomMDIForm(Control parent, ToolStripMenuItem menuItem) {
this.TopLevel = false;
this.Parent = parent;
this.StartPosition = FormStartPosition.CenterParent;
this.FormBorderStyle = FormBorderStyle.Sizable;
this.Dock = DockStyle.Fill;

this.MenuItem = menuItem;
}

private CustomMDIForm() {
}

protected override void OnLoad(EventArgs e) {
if ((this.MenuItem != null)) {
this.MenuItem.Enabled = false;
}
base.OnLoad(e);
}

protected override void Dispose(bool disposing) {
if ((this.MenuItem != null)) {
this.MenuItem.Enabled = true;
}
base.Dispose(disposing);
}

}

//=======================================================
//Service provided by Telerik (www.telerik.com)
//=======================================================

+
Código (csharp) [Seleccionar]
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;

public sealed class Form1 : Form {

private CustomMDIForm cf;

public Form1() {
Load += Form1_Load;
this.InitializeComponent();
this.cf = new CustomMDIForm(parent: this.Panel3, menuItem: this.nuevoClienteToolStripMenuItem);
}

private void Form1_Load(object sender, EventArgs e) {
this.cf.Show();
}
}

//=======================================================
//Service provided by Telerik (www.telerik.com)
//=======================================================



Saludos!
#4357
Los enlaces oficiales de las ISOs en Español de la build 10565 han sido publicados ayer hace pocas horas.



Aquí tienen para descargar :)

Windows 10 Insider Preview (x64) - Build 10565   Download (3.69 GB)   0xFCAD08F8954ECC5A8CDF951C0B447A54A4C88643
http://iso.esd.microsoft.com/W10IP/BF15E0F8F8F6B6BFCD0F62892BFBA60756371D21/Windows10_InsiderPreview_x64_ES-ES_10565.iso

Windows 10 Insider Preview (x86) - Build 10565   Download (2.76 GB)   0xB5A7BBB59E9CA9451B62B9CA28498B32C58347DD
http://iso.esd.microsoft.com/W10IP/BF15E0F8F8F6B6BFCD0F62892BFBA60756371D21/Windows10_InsiderPreview_x86_ES-ES_10565.iso

https://www.microsoft.com/en-us/software-download/windowsinsiderpreviewiso

Tema solucionado.




Solo me queda una última cosa por decirle a todos ustedes...

...NO SE INSTALEN LA BIRRIA DE LA RTM DE WINDOWS 10 BUILD 10240, tiene demasiados problemas estéticos que afectan a la experiencia del usuario, y otro tipo de bugs que han ido siendo corregidos durante estos meses.

Si les molestan estas pequeñas imperfecciones de Windos tanto como a mi, entonces lo mejor que pueden hacer es esperar a que salga la nueva release considerada "estable" en octubre o noviembre que ya llevará las actualizaciones/correcciones de la build 10565, o descargar directamente la build 10565, ya que a pesar de no ser una release es mucho mejor que la RTM de Windows 10 (build 10240), y lo cierto es que trae varios cambios positivos, no solamente estéticos.

Saludos!
#4358

  • 1. La condición del loop es incorrecta:
    while ((resultado1 <= 0) || (resultado2 <= 0));

    Se puede traducir cómo "Mientras que resultado1 sea igual o menor que 0, o resultado2 sea igual o menos que 0.". Nótese el "Mientras que". Necesitas hacer lo opuesto, "Hasta que".

  • 2. La lógica del búcle es incorrecta, indiferentemente de si la cantidad del stock se agota, tú seguirás preguntando y descontando.

Así lo puedes corregir:
Código (csharp) [Seleccionar]
static void Main(string[] args) {

   int penStock = 0;
   int pencilStock = 0;
   int penCount = 0;
   int pencilCount = 0;
   char userValue = '\0';

   Console.Write("¿Stock de bolígrafos: ");
   penStock = int.Parse(Console.ReadLine());

   Console.Write("¿Stock de lápìces: ");
   pencilStock = int.Parse(Console.ReadLine());

   while (!((penStock <= 0) || (pencilStock <= 0))) {

       Console.WriteLine();
       Console.Write("Introduce el producto a elegir (B o L): ");
       userValue = char.ToUpper(Console.ReadKey().KeyChar);

       if (new char[]{'B', 'L'}.Contains(userValue)) {

       Console.WriteLine();

       switch (userValue) {

       case 'B':
       Console.Write("Introduce cantidad a descontar de bolígrafos: ");
       penCount = int.Parse(Console.ReadLine());
       penStock = (penStock - penCount);
       break;

       case 'L':
       Console.Write("Introduce cantidad a descontar de lápìces: ");
       pencilCount = int.Parse(Console.ReadLine());
       pencilStock = (pencilStock - pencilCount);
       break;

               default:
                   // Do nothing...
                   break;

       }

       } else {
       Console.WriteLine("Error");

       }

   }

   Console.WriteLine();
   Console.WriteLine("Fin del programa");
   Console.WriteLine(string.Format("Quedan {0} bolígrafos y {1} lápices.", penStock, pencilStock));

   Console.ReadKey();
   Environment.Exit(0);

}


Sin embargo, yo lo haría más o menos así, de una manera simplificada y rehutilizable:

Código (csharp) [Seleccionar]
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;

+
Código (csharp) [Seleccionar]
public sealed class StockItem
{

public char Letter {
get { return this.letterB; }
}
private char letterB;

public string Description {
get { return this.descriptionB; }
}
private string descriptionB;

public int Amount { get; set; }

public StockItem(char letter, string description, ref int amount) {
this.letterB = letter;
this.descriptionB = description;
this.Amount = amount;
}

}

+
Código (csharp) [Seleccionar]
static void Main(string[] args) {
   
   int penCount = 0;
   int pencilCount = 0;

   List<StockItem> items = new List<StockItem> {
       new StockItem('B', "bolígrafos", ref penCount),
       new StockItem('L', "lápices", ref pencilCount)
   };
   StockItem selectedItem = default(StockItem);

   foreach (StockItem item in items) {
       Console.Write(string.Format("¿Stock de {0}: ", item.Description));
       item.Amount = int.Parse(Console.ReadLine());
   }

   while ((from item in items select item.Amount).Min() > 0)  {
     
       Console.WriteLine();
       Console.Write("Introduce el producto a elegir: ");
       char userInput = char.ToUpper(Console.ReadKey().KeyChar);

       selectedItem = items.FirstOrDefault(item => char.ToUpper(item.Letter).Equals(userInput));

       if ((selectedItem != null)) {
       Console.WriteLine();
       Console.Write(string.Format("Introduce cantidad a descontar de {0}: ", selectedItem.Description));
       selectedItem.Amount = (selectedItem.Amount - int.Parse(Console.ReadLine()));

       } else {
       Console.WriteLine("Error");

       }

   }

   Console.WriteLine();
   Console.WriteLine("Fin del programa.");

   StringBuilder sb = new StringBuilder();
   foreach (StockItem item in items) {
       sb.AppendLine(string.Format("Quedan {0} {1}.", item.Amount, item.Description));
   }
   Console.WriteLine(sb.ToString());

   Console.ReadKey();
   Environment.Exit(0);

}


Saludos
#4359
Cita de: solrac666 en 14 Octubre 2015, 11:20 AMel primero es de Van Halen, el titulo no me acuerdo, pero es una cancion bastante conocida.

Qué crack estás hecho!. muchas gracias, ya la encontré:

Van Halen - Ain't Talkin' 'Bout Love
[youtube=640,360]https://www.youtube.com/watch?v=SuyvT8nFMLY[/youtube]

Y mira que Van Halen nunca me llamó la atención pero había escuchado muchisimos temas de él por la banda Halestorm, pero no tenia ni idea de ese tema  :-[.

Saludos!
#4360
Multimedia / Re: Video con música de fondo
14 Octubre 2015, 07:54 AM
1. Grabas el video con tu voz.


2. Con cualquier editor de video, por ejemplo con AviDemux, extraes la pista de audio con tu voz.



3. Con cualquier editor de audio, por ejemplo con Audacity, cojes el archivo de música de fondo deseado y le insertas la voz de fondo.
   Hacer esto es tan sencillo como arrastrar dos archivos al programa, entonces cada audio será cargado en pistas paralelas y solo tienes que exportar el audio para guardar los cambios. Las pistas se fusionarán entre si automaticamente.

[youtube=640,360]https://www.youtube.com/watch?v=SM7ft8eN9RI[/youtube]


4. Con cualquier editor de video, por ejemplo con AviDemux, reemplazas la pista de audio antigua por la nueva que modificaste:
[youtube=640,360]https://www.youtube.com/watch?v=9rFAKH3V5p8[/youtube]
(mira a partir del minuto 01:20, simplemente cargas el video en el programa, y le das a "Select rack" para reemplazar la pista de audio)


PD: Si no te ves capaz, siempre puedes pasarme por aquí o por privado el video y la musica de fondo y yo te lo hago en un santiamén, aunque entonces no aprenderías nada :P.


Saludos!