lunes, abril 28, 2014

Mini-CRM en ASP.Net (II)

 

Creación del proyecto

Desde VS Express 2013 para Web he creado un nuevo proyecto de tipo aplicación ASP.Net MVC con autenticación de tipo cuentas de usuario individuales (es el defecto).

image

Al crear el proyecto se muestra una página con información sobre los siguientes pasos a realizar en el proyecto. A tener en cuenta en mi proyecto:

Si clicamos cualquiera de los 2 primeros nos redirige a:

[…]

This topic explains the options for creating ASP.NET web projects in Visual Studio 2013 with Update 2.

The new dialogs and templates include the following new features compared to earlier versions of Visual Studio:

[…]

En este momento nos centraremos en el apartado ASP.Net Identity.

 

Autenticación mediante ASP.Net Identity

ASP.Net Identity es el nuevo sistema de autenticación para ASP.Net, tal como se explica en ASP.Net Identity. Para añadirlo a mi proyecto sigo los pasos que se explican en el link:

Adding ASP.NET Identity to an Empty or Existing Web Forms Project

A modo de resumen:

  • Para añadir ASP.Net Identity al proyecto, se puede utilizar NuGet tal como muestra la siguiente figura:

image

  • Para mi proyecto cambio la conexión que viene por defecto por una a mi propia base de datos lo cual hace que se generen automáticamente en ella las tablas necesarias.

  • De momento no necesito utilizar proveedores externos (facebook, gmail…)

 

Bootstrap (responsive web)

Las plantillas de proyecto de Visual Studio 2013 utilizan Bootstrap para proporcionar un diseño responsive, lo que significa que los diseños pueden adaptarse dinámicamente a los diferentes tamaños de ventana del navegador, y por tanto, al tamaño de los diferentes dispositivos consumidores.

En bootswatch.com podemos encontrar temas alternativos al que aparece por defecto en nuestra nueva aplicación (aquí se explica en detalle cómo proceder). Además de los temas free también existen otros mucho más elaborados de pago (rondan los 8$ a 18$). Es el caso de los que podemos encontrar en {wrap}bootstrap. Para nuestro mini-CRM hemos comprado la plantilla denominada Ace.

WB0B30DGR[1]

Aquí hemos tenido que dedicar unas cuantas horas a encajar la nueva plantilla.

  1. Descomprimir el paquete descargado y añadirlo al proyecto

    image
  2. A partir de index.html de la nueva plantilla, crear una nueva vista _LayoutAce.cshtml con la que reemplazar la actual _Layout.cshtml. Para que el proyecto use la nueva plantilla, cambiar la una por la otra en _ViewStart.cshtml.
    @{
    Layout = "~/Views/Shared/_LayoutAce.cshtml"
    ;
    }



  3. Crear los bundles de la nueva plantilla (ver bundling and minification). Para ello en AppStart/BundleConfig.cs añadir las siguientes líneas:
    // Bundles de wrapbootstrap-Ace
    bundles.Add(new ScriptBundle("~/bundles/wrapbootstrap-ace/js").Include(
    "~/templates/wrapbootstrap_ace/assets/js/*.js", "~/templates/wrapbootstrap_ace/assets/js/uncompressed/*.js"));

    bundles.Add(
    new StyleBundle("~/bundles/wrapbootstrap-ace/css").Include(
    "~/templates/wrapbootstrap_ace/assets/css/*.css", "~/templates/wrapbootstrap_ace/assets/css/ace.min.css"));




  4. Adaptar _LayoutAce.cshtml. Cambios principales:

  5. […]
    <head>
    <
    meta charset="utf-8" />
    <
    title>Mi CRM</title>

    <
    meta name="description" content="Common form elements and layouts" />
    <
    meta name="viewport" content="width=device-width, initial-scale=1.0" />

    @Styles.Render("~/bundles/wrapbootstrap-ace/css")

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/jqueryval")
    @Scripts.Render("~/bundles/modernizr")
    @Scripts.Render("~/bundles/bootstrap")
    @Scripts.Render("~/bundles/wrapbootstrap-ace/js")

    […]



         <li class="light-blue">
      @Html.Partial("_LoginPartial"
      )
      </li><!—login-->




    […]

       <div class="page-content">
    @
    RenderBody()
    </div>
    <!-- /.page-content -->





  6. Adaptar la pantalla de inicio de sesión Account/Login.cshtml:

    @model NeumaliaCRM.Models.LoginViewModel

    @{
    Layout = null
    ;
    }
    @{
    ViewBag.Title = "Inicio de sesión"
    ;
    }

    <!DOCTYPE html
    >
    <
    html lang
    ="es">
    <
    head
    >
    <
    meta charset
    ="utf-8" />
    <
    title>CRM Neumalia - @ViewBag.Title</title
    >

    <
    meta name="description" content
    ="User login page" />
    <
    meta name="viewport" content
    ="width=device-width, initial-scale=1.0" />

    <!-- basic styles -->

    <link href="~/templates/wrapbootstrap_ace/assets/css/bootstrap.min.css" rel
    ="stylesheet" />
    <
    link rel="stylesheet" href
    ="~/templates/wrapbootstrap_ace/assets/css/font-awesome.min.css" />

    <!--[if IE 7]>
    <link rel="stylesheet" href="~/templates/wrapbootstrap_ace/assets/css/font-awesome-ie7.min.css" />
    <![endif]-->
    <!-- page specific plugin styles -->
    <!-- fonts -->

    <link rel="stylesheet" href
    ="~/templates/wrapbootstrap_ace/assets/css/ace-fonts.css" />

    <!-- ace styles -->

    <link rel="stylesheet" href
    ="~/templates/wrapbootstrap_ace/assets/css/ace.min.css" />
    <
    link rel="stylesheet" href
    ="~/templates/wrapbootstrap_ace/assets/css/ace-rtl.min.css" />

    <!--[if lte IE 8]>
    <link rel="stylesheet" href="~/templates/wrapbootstrap_ace/assets/css/ace-ie.min.css" />
    <![endif]-->
    <!-- inline styles related to this page -->
    <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
    <script src="~/templates/wrapbootstrap_ace/assets/js/html5shiv.js"></script>
    <script src="~/templates/wrapbootstrap_ace/assets/js/respond.min.js"></script>
    <![endif]-->

    @*extraído de site.css...
    *@
    <style
    >
    /* styles for validation helpers */
    .field-validation-error
    {
    color: #b94a48
    ;
    font-size:small
    ;
    }

    .field-validation-valid
    {
    display: none
    ;
    }

    input.input-validation-error
    {
    border: 1px solid #b94a48
    ;
    }

    input[type="checkbox"].input-validation-error
    {
    border: 0 none
    ;
    }

    .validation-summary-errors
    {
    color: #b94a48
    ;
    }

    .validation-summary-valid
    {
    display: none
    ;
    }
    </style
    >

    </
    head
    >

    <
    body class
    ="login-layout">
    <
    div class
    ="main-container">
    <
    div class
    ="main-content">
    <
    div class
    ="row">
    <
    div class
    ="col-sm-10 col-sm-offset-1">
    <
    div class
    ="login-container">
    <
    div class
    ="center">
    <
    h1
    >
    <
    i class="icon-group green"></i
    >
    <
    span class="red">CRM</span
    >
    <
    span class="white">Neumalia</span
    >
    </
    h1
    >
    <
    h4 class="blue">&copy; AutoEquip</h4
    >
    </
    div
    >

    <
    div class="space-6"></div
    >

    <
    div class
    ="position-relative">
    <
    div id="login-box" class
    ="login-box visible widget-box no-border">
    <
    div class
    ="widget-body">
    <
    div class
    ="widget-main">
    <
    h4 class
    ="header blue lighter bigger">
    <
    i class="icon-coffee green"></i
    >
    Introduce tus credenciales
    </h4
    >

    <
    div class="space-6"></div
    >

    @using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form"
    }))
    {
    @
    Html.AntiForgeryToken()
    @Html.ValidationSummary(true
    )

    <fieldset
    >
    <
    label class
    ="block clearfix">
    <
    span class
    ="block input-icon input-icon-right">
    @Html.TextBoxFor(m => m.UserName, new { @class = "form-control", @placeholder="Nombre de usuario"
    })
    @
    Html.ValidationMessageFor(m => m.UserName)
    <i class="icon-user"></i
    >
    </
    span
    >
    </
    label
    >

    <
    label class
    ="block clearfix">
    <
    span class
    ="block input-icon input-icon-right">
    @Html.PasswordFor(m => m.Password, new { @class = "form-control", @placeholder="Contraseña"
    })
    @
    Html.ValidationMessageFor(m => m.Password)
    <i class="icon-lock"></i
    >
    </
    span
    >
    </
    label
    >

    <
    div class="space"></div
    >

    <
    div class
    ="clearfix">

    @
    Html.CheckBoxFor(m => m.RememberMe)
    @
    Html.LabelFor(m => m.RememberMe)

    <button type="submit" class
    ="width-45 pull-right btn btn-sm btn-primary">
    <
    i class="icon-key"></i
    >
    Iniciar sesión
    </button
    >
    </
    div
    >

    <
    div class="space-4"></div
    >
    </
    fieldset
    >
    }

    </div>
    <!-- /widget-main -->

    </div>
    <!-- /widget-body -->
    </div>
    <!-- /login-box -->

    </div>
    <!-- /position-relative -->
    </div
    >
    </
    div>
    <!-- /.col -->
    </div>
    <!-- /.row -->
    </div
    >
    </
    div>
    <!-- /.main-container -->

    </body
    >
    </
    html>image



  7. Adaptar _LoginPartial.cshtml:
    @using Microsoft.AspNet.Identity
    @if
    (Request.IsAuthenticated)
    {
    using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right"
    }))
    {
    @
    Html.AntiForgeryToken()
    }

    <a data-toggle="dropdown" href="#" class
    ="dropdown-toggle">
    <
    img class="nav-user-photo" src="~/templates/wrapbootstrap_ace/assets/avatars/avatar2.png" alt
    ="Foto usuario" />
    <
    span class
    ="user-info">
    <
    small>Hola,</small
    >
    @
    User.Identity.GetUserName()
    </span
    >

    <
    i class="icon-caret-down"></i
    >
    </
    a
    >

    <
    ul class
    ="user-menu pull-right dropdown-menu dropdown-yellow dropdown-caret dropdown-close">
    <
    li
    >
    <
    a href
    ="/Account/Manage">
    <
    i class="icon-cog"></i
    >
    Configurar cuenta
    </a
    >
    </
    li
    >

    <
    li
    >
    <
    a href
    ="#">
    <
    i class="icon-user"></i
    >
    Perfil
    </a
    >
    </
    li
    >

    <
    li class="divider"></li
    >

    <
    li
    >
    <
    a href
    ="javascript:document.getElementById('logoutForm').submit()">
    <
    i class="icon-off"></i
    >
    Cerrar sesión
    </a
    >
    </
    li
    >
    </
    ul
    >
    }
    else
    {
    <ul class
    ="nav navbar-nav navbar-right">
    @*<li>@Html.ActionLink("Registrarse", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
    *@
    <li>@Html.ActionLink("Iniciar sesión", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li
    >
    </
    ul
    >
    }



  8. image 

Mini-CRM en ASP.Net (I)

 

Las próximas entradas de mi blog las dedicaré a recoger mi implementación de una aplicación ASP.Net de principio a fin. No pretende enseñar nada, simplemente reflejar mis observaciones, aprendizajes, conclusiones…

En esta primera entrega haré una pequeña introducción mencionando los requisitos tanto funcionales como técnicos, explicaré porqué he descartado utilizar alternativas ya existentes, y por último comentaré las herramientas de desarrollo, tecnologías y lenguaje de programación que utilizaré.

Requisitos

Mi empresa necesita una aplicación para sus comerciales que cumpla con los siguientes requisitos funcionales iniciales:

  • A cada comercial se le deben mostrar los datos referentes a los clientes de la(s) zona(s) que tiene asignada(s).
  • Top clientes.
  • Ventas/mes (unidades e importe), del año en curso y del anterior.
  • Ventas/marca, idem.
  • Ventas/canal, idem.
  • Ventas/provincia, idem.
  • Visitas a la web.
  • Histórico de facturas/presupuestos/pedidos.
  • Nuevo presupuesto/pedido para un cliente.

Y los siguientes requisitos técnicos:

  • Consumible desde fuera de la intranet, a ser posible desde distintos dispositivos (portátil/PC, tablet, móvil…)
  • Sólo pueden entrar usuarios autorizados (los comerciales).

 

1as decisiones: ¿desarrollo desde cero o utilizar algo ya existente?

Siempre que se pide una aplicación de este tipo debemos hacernos la pregunta de si no iremos a reinventar la rueda, es decir, si ya existen CRMs (tanto comerciales como open source) para qué vamos a implementar el nuestro propio.

En mi empresa hemos descartado las alternativas de pago por motivos presupuestarios (y por alguna mala experiencia anterior con un CRM muy conocido), y nos hemos dado un par de días para hacer un sondeo de las alternativas open source existentes.

Si buscamos CRMs en OpenPYME, codeplex.com o en sourgeforce.com encontraremos infinidad de alternativas. En nuestro caso hemos acotado la búsqueda centrándonos en aquellos que cumplen los siguientes requisitos:

  • Preferentemente desarrollados en .Net (porque es la tecnología que más dominamos).
  • Que estén en un estado correcto.

Después de mucho buscar, la opción que más me gustó fue Dolibarr. Uno de sus puntos fuertes es que es muy fácil de instalar y funciona a la primera. No me voy a extender en describir el producto porque en su web está muy bien detallado y porque para quien le interese creo que lo mejor es instalárselo y probarlo.

Dolibarr, no obstante, es mucho más que un CRM, es también ERP y más cosas. Y yo lo que necesito es simplemente un pequeño CRM, con una funcionalidad bastante limitada y concreta.

Examinando un poco más detenidamente el código de esta fantástica alternativa llego a la conclusión de que me voy a meter en un gran berenjenal intentando recortar todo aquello que no necesito, adaptando lo que sí y acabando de añadir lo que le falta para cumplir con mis requisitos.

En definitiva, para lo que mi empresa me pide, creo que tampoco en una buena solución lo de adoptar Dolibarr.

¿Y entonces?

Construiré mi propio mini-CRM.

 

Herramientas y tecnologías a utilizar

Dado que uno de los requisitos es que sea una aplicación que se pueda consumir desde diferentes tipos de dispositivos y a través de internet, crearemos una aplicación web (todos los CRMs que conozco lo son).

Y dado que la tecnología con la que más cómodos nos sentimos es .Net, crearemos un nuevo proyecto ASP.Net.

La herramienta de desarrollo natural (que además es gratuita!) es Visual Studio Express 2013 para Web, y es lo que utilizaré.