viernes, febrero 28, 2025

¿Qué es WSL? Una Introducción a Windows Subsystem for Linux

Si te encanta Linux pero no quieres/puedes renunciar a Windows, ahora tienes WSL (Subsistema de Windows para Linux), y es que Microsoft pasó hace unos años del odio a Linux al amor, llegando a crear un subsistema Linux embebido en Windows.


En el mundo tecnológico actual, los desarrolladores buscan constantemente herramientas que faciliten su trabajo y les permitan ser más productivos. Una de estas herramientas es el Windows Subsystem for Linux, más conocido como WSL. En esta entrada de blog, exploraremos qué es WSL, cómo funciona y por qué ha ganado popularidad entre los desarrolladores.

¿Qué es WSL?

Windows Subsystem for Linux (WSL) es una característica de Windows 10 y versiones posteriores que permite a los usuarios ejecutar un entorno Linux directamente en su sistema operativo Windows, sin necesidad de una máquina virtual. WSL facilita el uso de herramientas y aplicaciones de Linux en un entorno Windows, lo que lo convierte en una opción atractiva para desarrolladores, programadores y entusiastas de la tecnología.

Historia y Evolución

WSL fue introducido por primera vez en 2016 con la actualización de Windows 10 Anniversary Update. En sus primeras versiones, WSL 1 ofrecía una compatibilidad básica para ejecutar comandos de Linux. Sin embargo, con el lanzamiento de WSL 2 en mayo de 2019, Microsoft mejoró significativamente la capacidad de WSL al introducir un núcleo de Linux completo, lo que brinda un rendimiento y compatibilidad mucho mejores con aplicaciones Linux.

¿Cómo Funciona WSL?

WSL actúa como una capa de compatibilidad entre Windows y Linux. La primera versión, WSL 1, traduce las llamadas del sistema Linux en llamadas del sistema Windows, lo que significa que las aplicaciones de Linux podían ejecutarse en Windows sin hardware adicional. Con WSL 2, esta traducción se eliminó, y en su lugar, se utiliza una máquina virtual ligera que alberga un núcleo Linux real.

Cómo instalar una distro Linux en WSL

Lo primero será instalar el propio Subsistema de Windows para Linux. Esto lo podemos hacer escribiendo "wsl" en el buscador de Windows



Una vez que tenemos instalado el Subsistema de Windows para Linux, tendremos que instalar una distribución de Linux: Debian, Ubuntu... Si abrimos una ventana "cmd" y escribimos "wsl" se nos mostrará un mensaje indicando cómo debemos proceder a continuación.


Ejecutando el comando "wsl --list --online" podremos obtener una lista de las distros disponibles.



Yo he optado por instalar la distro de Debian, utilizando el comando "wsl --install Debian". En unos pocos segundos quedará instalada


Y se mostrará una ventana de bienvenida:



En esta ventana encontraremos recursos útiles sobre el Subsistema Linux, como por ejemplo "Integracion de Docker Desktop" e "Integración de VS Code"

NOTA: Para volver a esta pantalla de bienvenida, escribe "wsl Settings" en el búsqueda de Windows y se abrirá la pantalla de configuración de wsl. Desde el menú de la izquierda, abajo, verás la opción "Te damos la bienvenida a WSL". 

Podemos instalar otra distro (que se mantendrá como sistema independiente). 

Con el comando "wsl --list" obtendremos la lista de las distros instadas, indicándonos cual es la predeterminada.

Cómo abrir una distro Linux de WSL


Escribiendo "wsl" en cmd se abrirá la distro predeterminada. También podemos escribir "wsl -d Ubuntu" para abrir "Ubuntu" (suponiendo que la hayamos instalado). Otra forma de abrir alguna de ellas es escribiendo "wsl" en el buscador de windows, y veremos las diferentes distros instaladas y podremos ejecutar clicandola que deseemos.

La primera vez que ejecutemos la distro se nos pedirá un usuario y contraseña. Y a partir de ahí tendremos un sistema linux completo ejecutándose bajo nuestro Windows:


... desde el cual podemos acceder a nuestro sistema de archivos de Windows (y viceversa)




... Podremos instalar python ($ sudo apt-get install python3) e incluso podremos instalar y ejecutar programas con interfaz gráfica, como por ejemplo Visual Studio Code (del que hablaré en otro post).

Ventajas de Usar WSL


Integración: WSL permite ejecutar herramientas de Linux y Windows de manera conjunta. Puedes utilizar editores de texto de Linux al mismo tiempo que accedes a archivos y aplicaciones de Windows.

Desarrollo Multiplataforma: Ideal para desarrolladores que trabajan en entornos tanto de Windows como de Linux. WSL facilita la creación y prueba de aplicaciones web en ambos sistemas operativos.

Sin Necesidad de Virtualización: A diferencia de las máquinas virtuales que requieren más recursos y son más lentas, WSL es más ligero y eficiente, lo que permite una mejor experiencia de usuario.

Acceso a la Terminal de Linux: Muchos desarrolladores prefieren la terminal de Linux por su flexibilidad y potencia. WSL ofrece esta opción en un entorno nativo de Windows.


Conclusión


WSL ha revolucionado la forma en que los desarrolladores y usuarios de Windows interactúan con Linux. Al proporcionar un entorno integrado y eficiente que combina lo mejor de ambos mundos, WSL facilita el desarrollo y mejora la productividad. Si trabajas en tecnología, programación o simplemente estás interesado en explorar Linux, definitivamente vale la pena considerar WSL como parte de tus herramientas de trabajo.
¡Anímate a probarlo y explora el vasto mundo de posibilidades que WSL tiene para ofrecerte!


lunes, enero 18, 2016

Alternativas (free) a SugarCRM

SugarCRM ya no dispone de una versión "free", tal como se muestra en el cuadro editions and pricing de la compañía. Parece ser que la versión Community Edition a pasado a mejor vida... ;(

Por fortuna existen alternativas, gratuítas o de menor coste. Entre ellas he elegido algunas que ahora presento:


  • SplendidCRM: Dispone de una versión Community On Premise totalmente gratuíta y open-source. A diferencia de SugarCRM y las demás opciones que muestro, utiliza tecnología Microsoft, es decir, funciona sobre ASP.Net y MS SQL Server, y sus fuentes entán en C#.
  • Bitrix24: La versión Cloud es gratuíta hasta 12 usuarios, y la versión Standard no tiene límite de usuarios, con un coste de 99$/mes. Código fuente disponible.
  • SuiteCRM: Es una alternativa open source a SugarCRM, de hecho está basado en la versión Community Edition de Sugar. Es un producto open source, y según dicen en la propia web, siempre lo será. Tiene una interfaz gráfica muy cuidada, y preparada para funcionar en dispositivos móviles. Se puede acceder a demos desde aquí. Se puede descargar desde http://sourceforge.net/projects/suitecrm/.


(más info en http://uncommonbusiness.blogspot.com.es/2013/10/best-free-sugarcrm-alternatives.html y http://blog.capterra.com/free-and-open-source-crm/)

viernes, enero 15, 2016

Error debido a GRUB durante arranques de instalación de Windows 10

En mi nuevo puesto de trabajo me han asignado una máquina con Windows 7 no nueva. Al intentar instalar Windows 10 aparece la línea de comandos y "GRUB>" y no puedo seguir.

Para que arranque el nuevo sistema operativo hay que hacer:

GRUB> rootnoverify (hd0,0)
GRUB> makeactive
GRUB> chainloader +1
GRUB> boot

Con esto ya arranca, pero cada vez que haya que volver a arrancar tendremos volverá a ocurrir lo mismo. Para corregirlo se puede usar la utilidad "Dual-boot Repair 10", disponible en https://www.boyans.net/dual-boot-repair-windows-10.html. Se debe descargar de la url, descomprimirla y ejecutar "DualBootRepair". A continuación ejecutar Automatic Repair.

Dual-boot Repair tool 10


Como alternativa se puede utilizar la utilidad bootsect de Windows, tal como se explica en
http://superuser.com/questions/949219/how-to-fix-windows-10-boot-loader-from-windows.

miércoles, mayo 21, 2014

jQueryFlot en ASP.net (II) + AJAX

Siguiendo con la entrada anterior, supongamos ahora que queremos actualizar los datos de la página en función de, por ejemplo, que se seleccione un rango de fechas u otro. Y clara está, esta actualización queremos hacerla sin tener que recargar la página, es decir, mediante AJAX. Tendremos que hacer las siguientes modificaciones:

  • Nueva función TopMarcasJson en el controlador VentasController que retorne un objeto de tipo JsonResult que será consumido directamente por la función $.plot desde la vista
  • Modificar la función Top del controlador VentasController para que pase la colección TopMarcas a la vista (será la vista la que vaya a buscar los datos del modelo con la función anterior).
  • Modificar la vista Ventas/Top para que la función $.plot tome los datos mediante AJAX.

Nueva función TopMarcasJson en VentasController

        /// <summary>
///
Retorna JSON que se consume mediante AJAX desde la función $.plot de la vista Ventas/Top
/// </summary>
/// <returns></returns>
[HttpGet]
public JsonResult TopMarcasJson(int año)
{
var currentUserId = User.Identity.GetUserId();
var currentUser = _userManager.FindById(User.Identity.GetUserId());
var currentUserZonas = currentUser.Zonas.Select(z => z.ZonaId);

var ventasPorMarca = _ctx.Ventas
.Where(v => v.Año == año)
.Where(v => currentUserZonas.Contains(v.Zona))
// Incluir sólo los de las zonas del comercial logado
.GroupBy(v => v.Marca)
.Select(v =>
new VentaMarcaViewModel()
{
Marca = v.FirstOrDefault().Marca,
Gama = v.FirstOrDefault().Gama,
Importe = v.Sum(g => g.Importe),
Cantidad = v.Sum(g => g.Cantidad)
})
.OrderByDescending(c => c.Importe);

// Top 10 ventas por marca
var topVentasPorMarca = ventasPorMarca
.Take(10)
// Top 10
.ToList();

// Resto: consolidar como uno sólo
var restoTopVentasPorMarca = ventasPorMarca
.Skip(10)
.Select(v =>
new { Marca = "Resto", v.Importe, v.Cantidad })
.GroupBy(v => v.Marca)
// Agrupar el resto
.Select(v => new VentaMarcaViewModel()
{
Marca =
"Resto",
Gama =
"",
Importe = v.Sum(g => g.Importe),
Cantidad = v.Sum(g => g.Cantidad)
})
.ToList()
// Lista con un único el elemento, el consolidado
.FirstOrDefault(); // Coge el primer (y único elemento).

// Añadir a top ventas el consolidado con el resto.
if (restoTopVentasPorMarca != null)
topVentasPorMarca.Add(restoTopVentasPorMarca);

// Convertir a Json de tipo array de {label, data}
return Json(
topVentasPorMarca.Select(v =>
new { label = v.Marca, data = v.Importe }),
JsonRequestBehavior.AllowGet
);
}

Modificar la función Top de VentasController


Ya no hará falta pasar la colección TopMarcas desde esta función, por lo que podemos suprimir el fragmento de código que hace referencia a eso y retornar la vista vacía (sin modelo).


Modificar script de la vista Ventas/Top


Debemos sustituir el fragmento bajo /** Recoger datos y dibujar **/ por el nuevo:

        function drawPieChart(placeholder, data0, position) {
$.getJSON(
"TopMarcasJson", function (data) {
//succes - data loaded, now use plot:
$.plot(placeholder, data, {
series: {
pie: {
show:
true,
tilt: 0.8,
highlight: {
opacity: 0.25
},
stroke: {
color:
'#fff',
width: 2
},
startAngle: 2
}
},
legend: {
show:
true,
position: position ||
"ne",
labelBoxBorderColor:
null,
margin: [-30, 15]
},
grid: {
hoverable:
true,
clickable:
true
}
})
            });
}
drawPieChart(placeholder);
Obsérvese cómo se llama a la función $.getJSON y el retorno obtenido se le pasa a $.plot.

jQueryFlot en ASP.net

En esta entrada explicaré como crear una vista ASP.net como la que se muestra a continuación, en la que se muestra la lista de las 10 marcas más vendidas más un gráfico de tipo pastel. Para ello utilizo jQueryFlot sobre la plantilla wrapbootstrap-ace comentada en entradas anteriores.

En este caso se tratará de una página “estática”, que recibirá los datos a través del Model. En una entrada posterior utilizaremos json para poder actualizar los datos (y el gráfico) de la vista en función de, por ejemplo, el rango temporal que elija el usuario (este año, últimos 30 días, etc.).

image

 

Vista SQL CRM_Ventas

Partimos de una vista de SQL Server existente, cuya de definición es la siguiente:

CREATE VIEW [dbo].[CRM_Ventas]
WITH SCHEMABINDING
AS
SELECT TOP
(100) PERCENT
Comercial as Zona,
[Clasif_ neumático] as Gama,
Marca,
c.Año,
c.Mes,
c.[Nº cliente] as IdCliente,
c.[Nombre cliente] as NombreCliente,
cast(SUM(c.[Imp_ ventas]) as decimal(7,2)) as Importe,
cast(SUM(c.[Cdad_ ventas]) as integer) as Cantidad
FROM dbo.[Cuadro mando Neumalia] AS c
GROUP BY Comercial, [Clasif_ neumático], Marca, c.Año, c.Mes, c.[Nº cliente], c.[Nombre cliente]
HAVING ([Clasif_ neumático] <> '')

Crear el modelo Venta


Utilizamos Code First desde base de datos para generar la entidad Venta a partir de la vista anterior, siguiendo los siguientes pasos:



  1. En la carpta Models –> Agregar / Nuevo elemento…

  2. ADO.Net Entity Data Model, con nombre = Ventas.

  3. Code First desde base de datos.

  4. Seleccionar la conexión de base de datos que nos interese.

  5. Seleccionar la vista CRM_Ventas

Debe quedarnos algo así:

namespace NeumaliaCRM.Models
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Spatial;

[
Table("CRM_Ventas")]
public partial class Venta
{
[
Key]
[
Column(Order = 0)]
[
StringLength(10)]
public string Zona { get; set; }

[
Key]
[
Column(Order = 1)]
[
StringLength(30)]
public string Gama { get; set; }

[
Key]
[
Column(Order = 2)]
[
StringLength(10)]
public string Marca { get; set; }

[
Key]
[
Column(Order = 3)]
[
DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Año { get; set; }

[
Key]
[
Column(Order = 4)]
[
DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Mes { get; set; }

[
Key]
[
Column(Order = 5)]
[
StringLength(20)]
public string IdCliente { get; set; }

[
Key]
[
Column(Order = 6)]
[
StringLength(30)]
public string NombreCliente { get; set; }

public decimal? Importe { get; set; }

public int? Cantidad { get; set; }
}
}

Crear el viewmodel TopVentasViewModel


La vista mostrará el top ventas por marca (tal como se muestra en la figura) y también el top ventas por cliente. Crearemos un viewmodel con la siguiente definición:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace NeumaliaCRM.ViewModels
{
public class TopVentasViewModel
{
public TopVentasViewModel(IList<VentaClienteViewModel> topClientes, IList<VentaMarcaViewModel> topMarcas)
{
TopClientes = topClientes;
TopMarcas = topMarcas;
}

public virtual IList<VentaClienteViewModel> TopClientes { get; set; }
public virtual IList<VentaMarcaViewModel> TopMarcas { get; set; }
}

public class VentaClienteViewModel
{
public string NombreCliente { get; set; }
public decimal? Importe { get; set; }
public int? Cantidad { get; set; }
}

public class VentaMarcaViewModel
{
public string Marca { get; set; }
public string Gama { get; set; }
public decimal? Importe { get; set; }
public int? Cantidad { get; set; }
}
}

Añadir el método Top al controlador VentasController

        //************//
// Top ventas //
//************//
public ActionResult Top(int? año = 2014)
{
var currentUserId = User.Identity.GetUserId();
var currentUser = _userManager.FindById(User.Identity.GetUserId());
var currentUserZonas = currentUser.Zonas.Select(z => z.ZonaId);

var topClientes = _ctx.Ventas
.Where(v => v.Año == año)
.Where(v => currentUserZonas.Contains(v.Zona))
// Incluir sólo los de las zonas del comercial logado
.GroupBy(v => v.IdCliente)
.Select(v =>
new VentaClienteViewModel()
{
NombreCliente = v.FirstOrDefault().NombreCliente,
Importe = v.Sum(g => g.Importe),
Cantidad = v.Sum(g => g.Cantidad)
})
.OrderByDescending(c => c.Importe)
.Take(10)
.ToList();

//////////////////////
// Ventas por marca //
//////////////////////

var ventasPorMarca = _ctx.Ventas
.Where(v => v.Año == año)
.Where(v => currentUserZonas.Contains(v.Zona))
// Incluir sólo los de las zonas del comercial logado
.GroupBy(v => v.Marca)
.Select(v =>
new VentaMarcaViewModel()
{
Marca = v.FirstOrDefault().Marca,
Gama = v.FirstOrDefault().Gama,
Importe = v.Sum(g => g.Importe),
Cantidad = v.Sum(g => g.Cantidad)
})
.OrderByDescending(c => c.Importe);

// Top 10 ventas por marca
var topVentasPorMarca = ventasPorMarca
.Take(10)
// Top 10
.ToList();

// Resto: consolidar como uno sólo
var restoTopVentasPorMarca = ventasPorMarca
.Skip(10)
.Select(v =>
new { Marca = "Resto", v.Importe, v.Cantidad })
.GroupBy(v => v.Marca)
// Agrupar el resto
.Select(v => new VentaMarcaViewModel()
{
Marca =
"Resto",
Gama =
"",
Importe = v.Sum(g => g.Importe),
Cantidad = v.Sum(g => g.Cantidad)
})
.ToList()
// Lista con un único el elemento, el consolidado
.FirstOrDefault(); // Coge el primer (y único elemento).

// Añadir a top ventas el consolidado con el resto.
if (restoTopVentasPorMarca != null)
topVentasPorMarca.Add(restoTopVentasPorMarca);

return View(new TopVentasViewModel(
topClientes.ToList(),
topVentasPorMarca.ToList())
);
}

Crear la vista Ventas/Top


En mi caso he creado 2 vistas en la carpeta Views/Ventas: Top, y una parcial, TopMarcas (podría haberse hecho con una sola, pero en mi caso me sirve para luego crear “clones” de TopMarcas) con la siguiente definición para cada una de ellas. Obsérvese que la función $.plot requiere que se le pasen los datos en formato json, como una lista de objetos con las propiedades label y data; con código de servidor recorremos la colección Model.TopMarcas y a continuación utilizamos el helper @Html.Raw(Json.Encode(…)) para transformar el IList pasado desde el controller a un objeto json.


Views/Ventas/Top

@model NeumaliaCRM.ViewModels.TopVentasViewModel

<!DOCTYPE html>

<
html>
<
head>
<
meta name="viewport" content="width=device-width" />
<
title>Index</title>

@Scripts.Render("~/bundles/wrapbootstrap-ace/js/flot")
<script type="text/javascript">
jQuery(function ($) {
$(
'.easy-pie-chart.percentage').each(function () {
var $box = $(this).closest('.infobox');
var barColor = $(this).data('color') || (!$box.hasClass('infobox-dark') ? $box.css('color') : 'rgba(255,255,255,0.95)');
var trackColor = barColor == 'rgba(255,255,255,0.95)' ? 'rgba(255,255,255,0.25)' : '#E2E2E2';
var size = parseInt($(this).data('size')) || 50;
$(
this).easyPieChart({
barColor: barColor,
trackColor: trackColor,
scaleColor:
false,
lineCap:
'butt',
lineWidth: parseInt(size / 10),
animate:
/msie\s*(8|7|6)/.test(navigator.userAgent.toLowerCase()) ? false : 1000,
size: size
});
})

$(
'.sparkline').each(function () {
var $box = $(this).closest('.infobox');
var barColor = !$box.hasClass('infobox-dark') ? $box.css('color') : '#FFF';
$(
this).sparkline('html', { tagValuesAttribute: 'data-values', type: 'bar', barColor: barColor, chartRangeMin: $(this).data('min') || 0 });
});

var placeholder = $('#piechart-placeholder').css({ 'width': '90%', 'min-height': '150px' });

/** Recoger datos y dibujar **/
        @{
var topMarcasJsonForPieChart = new List<object>();
foreach (var m in Model.TopMarcas)
{
topMarcasJsonForPieChart.Add(
new { label = m.Marca, data = m.Importe });
}
}
var data = @Html.Raw(Json.Encode(topMarcasJsonForPieChart))

function drawPieChart(placeholder, data, position) {
$.plot(placeholder,
data,
{
series: {
pie: {
show:
true,
tilt: 0.8,
highlight: {
opacity: 0.25
},
stroke: {
color:
'#fff',
width: 2
},
startAngle: 2
}
},
legend: {
show:
true,
position: position ||
"ne",
labelBoxBorderColor:
null,
margin: [-30, 15]
},
grid: {
hoverable:
true,
clickable:
true
}
})
}
drawPieChart(placeholder, data);

/**
we saved the drawing function and the data to redraw with different position later when switching to RTL mode dynamically
so that's not needed actually.
*/

            placeholder.data('chart', data);
placeholder.data('draw', drawPieChart);

var $tooltip = $("<div class='tooltip top in'><div class='tooltip-inner'></div></div>").hide().appendTo('body');
var previousPoint = null;

placeholder.on(
'plothover', function (event, pos, item) {
if (item) {
if (previousPoint != item.seriesIndex) {
previousPoint = item.seriesIndex;
var tip = item.series['label'] + " : " + item.series['percent'] + '%';
$tooltip.show().children(0).text(tip);
}
$tooltip.css({ top: pos.pageY + 10, left: pos.pageX + 10 });
}
else {
$tooltip.hide();
previousPoint =
null;
}
});

$(
'#recent-box [data-rel="tooltip"]').tooltip({ placement: tooltip_placement });
function tooltip_placement(context, source) {
var $source = $(source);
var $parent = $source.closest('.tab-content')
var off1 = $parent.offset();
var w1 = $parent.width();

var off2 = $source.offset();
var w2 = $source.width();

if (parseInt(off2.left) < parseInt(off1.left) + parseInt(w1 / 2)) return 'right';
return 'left';
}

$(
'.dialogs,.comments').slimScroll({
height:
'300px'
});

})
</script>

</
head>
<
body>
   @Html.Partial("TopClientes", Model.TopClientes)
@Html.Partial("TopMarcas", Model.TopMarcas)
</body>
</
html>

Views/Ventas/TopMarcas

@model IEnumerable<NeumaliaCRM.ViewModels.VentaMarcaViewModel>

<div class="col-sm-5">
<
div class="widget-box">
<
div class="widget-header widget-header-flat widget-header-small">
<
h5>
<
i class="icon-signal"></i>
Top 10 marcas
</h5>

<
div class="widget-toolbar no-border">
<
button class="btn btn-minier btn-primary dropdown-toggle" data-toggle="dropdown">
Este año
<i class="icon-angle-down icon-on-right bigger-110"></i>
</
button>

<
ul class="dropdown-menu pull-right dropdown-125 dropdown-lighter dropdown-caret">
<
li class="active">
<
a href="#" class="blue">
<
i class="icon-caret-right bigger-110">&nbsp;</i>
Este año
</a>
</
li>

<
li>
<
a href="#">
<
i class="icon-caret-right bigger-110 invisible">&nbsp;</i>
Año anterior
</a>
</
li>

<
li>
<
a href="#">
<
i class="icon-caret-right bigger-110 invisible">&nbsp;</i>
Este mes
</a>
</
li>

<
li>
<
a href="#">
<
i class="icon-caret-right bigger-110 invisible">&nbsp;</i>
Siempre
</a>
</
li>
</
ul>
</
div>
</
div>

<
div class="widget-body">
<
div class="widget-main">
<
div>
<
table class="table">
<
tr>
<
th>
@Html.DisplayNameFor(model => model.Marca)
</th>
<
th>
@Html.DisplayNameFor(model => model.Gama)
</th>
<
th>
@Html.DisplayNameFor(model => model.Importe)
</th>
<
th>
@Html.DisplayNameFor(model => model.Cantidad)
</th>
<
th></th>
</
tr>

@foreach (var item in Model)
{
<tr>
<
td>
@Html.DisplayFor(modelItem => item.Marca)
</td>
<
td>
@Html.DisplayFor(modelItem => item.Gama)
</td>
<
td>
@Html.DisplayFor(modelItem => item.Importe)
</td>
<
td>
@Html.DisplayFor(modelItem => item.Cantidad)
</td>
</
tr>
}

</table>
</
div>

<div id="piechart-placeholder"></div>

<
div class="hr hr8 hr-double"></div>

<
div class="clearfix">
<
div class="grid3">
<
span class="grey">
<
i class="icon-facebook-sign icon-2x blue"></i>
&nbsp; likes
</span>
<
h4 class="bigger pull-right">1,255</h4>
</
div>

<
div class="grid3">
<
span class="grey">
<
i class="icon-twitter-sign icon-2x purple"></i>
&nbsp; tweets
</span>
<
h4 class="bigger pull-right">941</h4>
</
div>

<
div class="grid3">
<
span class="grey">
<
i class="icon-pinterest-sign icon-2x red"></i>
&nbsp; pins
</span>
<
h4 class="bigger pull-right">1,050</h4>
</
div>
</
div>
</
div><!-- /widget-main -->
</div><!-- /widget-body -->
</div><!-- /widget-box -->
</div>

 

martes, mayo 20, 2014

Mini-CRM en ASP.Net (IV) “En construcción!”

Añadiendo la entidad VentasPorMarca

Los comerciales necesitan el cuadro de ventas por marca, en el que aparezcan cantidad e importe del año en curso y del anterior. Mis datos están en una tabla en la que cada línea es una agrupación de ventas por ejercicio, mes, zona y marca. Lo primero que necesito es una vista en que se muestren las ventas por zona y marca, del año en curso y del anterior. La creo en la base de datos así:

CREATE VIEW [dbo].[CRM_VentasPorMarcaYZona]
WITH SCHEMABINDING
AS
SELECT TOP
(100) PERCENT zm.[Clasif_ neumático] as Gama, zm.Marca, zm.Comercial as Zona, vPrev.i AS ImporteAnt, vPrev.c AS CantidadAnt, vCurr.i AS ImporteAct
,
vCurr.c AS
CantidadAct
FROM (SELECT Marca, [Clasif_ neumático],
Comercial
FROM dbo.[Cuadro mando Neumalia] AS
c
GROUP BY [Clasif_ neumático], Marca,
Comercial
HAVING ([Clasif_ neumático] <> '')) AS zm
LEFT OUTER JOIN
(
SELECT Año, Marca AS m, Comercial AS z, SUM([Imp_ ventas]) AS i, SUM(cast([Cdad_ ventas] as integer)) AS
c
FROM dbo.
[Cuadro mando Neumalia]
GROUP BY Año, Marca,
Comercial
HAVING (Año = DATEPART(year, GETDATE()) - 1)) AS vPrev ON zm.Marca = vPrev.m AND zm.Comercial = vPrev.z
LEFT OUTER JOIN
(
SELECT Año, Marca AS m, Comercial AS z, SUM([Imp_ ventas]) AS i, SUM(cast([Cdad_ ventas] as integer)) AS
c
FROM dbo.[Cuadro mando Neumalia] AS
[Cuadro mando Neumalia_1]
GROUP BY Año, Marca,
Comercial
HAVING (Año = DATEPART(year, GETDATE()))) AS vCurr ON zm.Marca = vCurr.m AND zm.Comercial = vCurr.
z
ORDER BY zm.Marca

A continuación creo la clase VentasPorMarca:

namespace NeumaliaCRM.Models
{
using
System;
using
System.Collections.Generic;
using
System.ComponentModel.DataAnnotations;
using
System.ComponentModel.DataAnnotations.Schema;
using
System.Data.Entity.Spatial;

[
Table("CRM_VentasPorMarcaYZona"
)]
public partial class
VentasPorMarca
{
[
Key
]
[
Column
(Order = 0)]
[
StringLength
(30)]
public string Gama { get; set
; }

[
Key
]
[
Column
(Order = 1)]
[
StringLength
(10)]
public string Marca { get; set
; }

[
Key
]
[
Column
(Order = 2)]
[
StringLength
(10)]
public string Zona { get; set
; }

public decimal? ImporteAnt { get; set
; }

public int? CantidadAnt { get; set
; }

public decimal? ImporteAct { get; set
; }

public int? CantidadAct { get; set
; }
}
}

También se puede crear a partir de la vista añadiendo