diff --git a/App_Code/ClsGenerales.vb b/App_Code/ClsGenerales.vb index 5ce060b9a..1e444e23b 100644 --- a/App_Code/ClsGenerales.vb +++ b/App_Code/ClsGenerales.vb @@ -399,15 +399,43 @@ Public Class ClsGenerales Try SqlAdapter.Fill(DSet) - DSet.Tables(0).TableName = "Indicadores" - DSet.Tables(1).TableName = "DetalleDia" - DSet.Tables(2).TableName = "DetalleAnualesMes" - DSet.Tables(3).TableName = "VentasPorVendedor" - DSet.Tables(4).TableName = "VentasPorClasificacion" - DSet.Tables(5).TableName = "VentasPorMarca" - DSet.Tables(6).TableName = "VentasPorPS" - DSet.Tables(7).TableName = "VentasPorCliente" - DSet.Tables(8).TableName = "VentasPorProveedor" + If DSet.Tables.Count > 0 Then + Select Case Opcion + Case 1 ' Cuentas por Cobrar + DSet.Tables(0).TableName = "Indicadores" + If DSet.Tables.Count > 1 Then DSet.Tables(1).TableName = "DetalleDia" + If DSet.Tables.Count > 2 Then DSet.Tables(2).TableName = "DetalleAnualesMes" + If DSet.Tables.Count > 3 Then DSet.Tables(3).TableName = "VentasPorVendedor" + If DSet.Tables.Count > 4 Then DSet.Tables(4).TableName = "VentasPorClasificacion" + If DSet.Tables.Count > 5 Then DSet.Tables(5).TableName = "VentasPorMarca" + If DSet.Tables.Count > 6 Then DSet.Tables(6).TableName = "VentasPorPS" + If DSet.Tables.Count > 7 Then DSet.Tables(7).TableName = "VentasPorCliente" + If DSet.Tables.Count > 8 Then DSet.Tables(8).TableName = "VentasPorProveedor" + + Case 2 ' Recuperación + DSet.Tables(0).TableName = "CarteraPorSubproyecto" + If DSet.Tables.Count > 1 Then DSet.Tables(1).TableName = "CarteraPorVendedor" + If DSet.Tables.Count > 2 Then DSet.Tables(2).TableName = "RecuperacionPorSubproyecto" + If DSet.Tables.Count > 3 Then DSet.Tables(3).TableName = "RecuperacionPorVendedor" + If DSet.Tables.Count > 4 Then DSet.Tables(4).TableName = "RecuperacionPorCliente" + If DSet.Tables.Count > 5 Then DSet.Tables(5).TableName = "CxCMensual" + + + Case 3 'Compras + If DSet.Tables.Count > 1 Then DSet.Tables(1).TableName = "ComprasPorClasificación" + If DSet.Tables.Count > 2 Then DSet.Tables(2).TableName = "ComprasPorMarca" + If DSet.Tables.Count > 3 Then DSet.Tables(3).TableName = "ComprasPorPS" + If DSet.Tables.Count > 4 Then DSet.Tables(4).TableName = "ComprasPorProveedor" + + Case 4 'Cuentas Por Pagar + DSet.Tables(0).TableName = "CxPPorSubproyecto" + If DSet.Tables.Count > 1 Then DSet.Tables(1).TableName = "CxPPorProveedor" + If DSet.Tables.Count > 2 Then DSet.Tables(2).TableName = "PagosPorSubproyecto" + If DSet.Tables.Count > 3 Then DSet.Tables(3).TableName = "PagosPorProveedor" + If DSet.Tables.Count > 4 Then DSet.Tables(4).TableName = "CxPMensual" + End Select + End If + Catch ex As Exception ' Manejo de errores diff --git a/App_Code/ClsVentas.vb b/App_Code/ClsVentas.vb index 8b83de472..f6abba49c 100644 --- a/App_Code/ClsVentas.vb +++ b/App_Code/ClsVentas.vb @@ -7728,7 +7728,7 @@ Public Class ClsVentas End Function - Public Function GetSMSIndicadores(Opcion As Integer, FechaInicio As Date, FechaFin As Date, CveUsuario As Integer, CveVendedor As Integer, CveGerenteVendedor As Integer, CveUsuarioTecnico As Integer, CveEmpresa As Integer) As Boolean + Public Function GetSMSIndicadores(Opcion As Integer, FechaInicio As Date, FechaFin As Date, CveUsuario As Integer, CveVendedor As Integer, CveUsuarioTecnico As Integer, CveEmpresa As Integer) As Boolean Dim Retorno As Boolean = False DataAdapter("PA_SMS_IndicadoresGet") With SqlAdapter.SelectCommand.Parameters @@ -7737,7 +7737,6 @@ Public Class ClsVentas .Add("@FechaFin", SqlDbType.Date).Value = FechaFin .Add("@CveUsuario", SqlDbType.Int).Value = CveUsuario .Add("@CveVendedor", SqlDbType.Int).Value = CveVendedor - .Add("@CveGerenteVendedor", SqlDbType.Int).Value = CveGerenteVendedor .Add("@CveUsuarioTecnico", SqlDbType.Int).Value = CveUsuarioTecnico .Add("@CveEmpresa", SqlDbType.Int).Value = CveEmpresa End With diff --git a/General/Dashboard.aspx b/General/Dashboard.aspx index 213c8a410..f3052b6ab 100644 --- a/General/Dashboard.aspx +++ b/General/Dashboard.aspx @@ -41,523 +41,16 @@ padding-right: 15px; } - .blue { - background: #3598dc; - } - - .green { - background: #32c5d2; - } - - .purple { - background: #8e44ad; - } - - .red { - background: #e7505a; - } - - .indicador-valor { - font-size: 24px; - font-weight: bold; - } - - .indicador-detalle { - margin-top: 10px; - } - - - .unoaunoold { - color: #999b9b; - font-size: 11px; - } - - .Dia { - background-image: url(/Imagenes/FondoCal.jpg); - background-repeat: repeat-x; - height: 17px; - font-size: 10px; - text-align: right; - } - - .Hora { - background-color: #E3EFFF; - height: 30px; - font-size: 10px; - border-right: solid 1px #5D8CC9; - border-bottom: solid 1px #5D8CC9; - } - - .Mes { - background-image: url(/Imagenes/Caption.jpg); - height: 27px; - } - .Link { color: #0000b9; } - .calendariopanel { - z-index: 999; - width: 100%; - height: 100%; - top: 0px; - left: 0px; - border: 0px; - background-color: transparent; - position: absolute; - } - - .calendarioback { - background-color: #000; - z-index: -90; - top: 0px; - opacity: 0.3; - position: fixed; - width: 100%; - height: 100%; - } - - .calendariobody { - width: 520px; - display: table; - background-color: #fff; - margin-top: 7%; - margin-left: 28%; - } - - .calendariobody label { - font-size: 12px; - } - - .tareasinicio tr td:nth-child(1) { - } - .portlet.light > .portlet-title > .caption > .caption-subject { font-size: 16px; } - .fix > div { - height: inherit !important; - width: inherit !important; - } - - - .fix > div > .ajax__html_editor_extender_texteditor { - height: 150px !important; - } - - /*#overlay a:hover { - position: fixed; - display: none; - width: 100%; - height: 100%; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0,0,0,0.5); - z-index: 2; - cursor: pointer; - }*/ - - - #moopio a { - display: block; - } - - #moopio a:hover { - background-color: rgba(0,0,0,0.5); - transform: scale(1.2); - cursor: default; - } - - - .MultiLineTextBox { - resize: none; - } - - - .select2-container--open { - z-index: 1000 !important; - } - - - - - #divsmsdash { - /* Estilo general compacto */ - --hcg-font-size: 8px !important; - --hcg-padding: 4px; - --hcg-border-radius: 6px; - } - .dashboard-container { - font-size: 16px; /* Tamaño base */ - } - - .highcharts-data-table table { - /* Estilo general compacto */ - --hcg-font-size: 8px !important; - --hcg-padding: 4px; - --hcg-border-radius: 6px; - } - - #divsmsdash .hcg-theme-default, .highcharts-datagrid-theme-default { - --hcg-font-size: 10px; - --hcg-padding: 5px; - } - - .highcharts-figure, - .highcharts-data-table table { - min-width: 310px; - max-width: 800px; - margin: 1em auto; - } - - #container { - height: 400px; - } - - .highcharts-data-table table { - font-family: Verdana, sans-serif; - border-collapse: collapse; - border: 1px solid #ebebeb; - margin: 10px auto; - text-align: center; - width: 100%; - max-width: 500px; - } - - .highcharts-data-table caption { - padding: 1em 0; - font-size: 1.2em; - color: #555; - } - - .highcharts-data-table th { - font-weight: 600; - padding: 0.5em; - } - - .highcharts-data-table td, - .highcharts-data-table th, - .highcharts-data-table caption { - padding: 0.5em; - } - - .highcharts-data-table thead tr, - .highcharts-data-table tr:nth-child(even) { - background: #f8f8f8; - } - - .highcharts-data-table tr:hover { - background: #f1f7ff; - } - - .highcharts-description { - margin: 0.3rem 10px; - } - - h1#title { - padding-top: 10px; - margin: 0; - background-color: #3d3d3d; - text-align: center; - color: #fff; - } - - #kpi-vitamin-a .highcharts-dashboards-component-title::before, - #kpi-iron .highcharts-dashboards-component-title::before { - width: 14px; - height: 14px; - border-radius: 28px; - display: inline-block; - padding: 0; - margin-right: 4px; - background-color: var(--highcharts-color-0); - content: " "; - } - - #kpi-iron .highcharts-dashboards-component-title::before { - background-color: var(--highcharts-color-2); - } - - #kpi-vitamin-a .highcharts-dashboards-component-kpi-subtitle, - #kpi-iron .highcharts-dashboards-component-kpi-subtitle { - margin-top: 10px; - font-size: 1.2em; - color: var(--highcharts-neutral-color-60); - } - - #kpi-vitamin-a .highcharts-dashboards-component-kpi-value, - #kpi-iron .highcharts-dashboards-component-kpi-value { - font-size: 4em; - line-height: 2.4rem; - margin-top: 10px; - color: var(--highcharts-color-0); - } - - #kpi-iron .highcharts-dashboards-component-kpi-value { - color: var(--highcharts-color-2); - } - - #dashboard-col-1 .highcharts-color-0 { - fill: var(--highcharts-color-2); - stroke: var(--highcharts-color-2); - } - - #kpi-vitamin-a .highcharts-dashboards-component-kpi-value::after, - #kpi-iron .highcharts-dashboards-component-kpi-value::after { - content: ""; - display: block; - font-size: 1rem; - } - - .highcharts-plot-line { - stroke-dasharray: 2px; - stroke: var(--highcharts-color-3); - } - - .highcharts-plot-line-label { - fill: var(--highcharts-color-3); - } - - .highcharts-description { - padding: 0 20px; - } - - #kpi-vitamin-a, - #kpi-iron { - flex: 1 1 100%; - height: 205px; - } - - /* LARGE */ - @media (max-width: 1200px) { - #dashboard-col-0, - #dashboard-col-1 { - flex: 1 1 35%; - } - - #kpi-wrapper { - flex: 1 1 30%; - } - - #kpi-vitamin-a, - #kpi-iron { - flex: 1 1 100%; - } - } - - /* MEDIUM */ - @media (max-width: 992px) { - #dashboard-col-0, - #dashboard-col-1 { - flex: 1 1 50%; - } - - #kpi-wrapper { - flex: 1 1 100%; - } - - #kpi-vitamin-a, - #kpi-iron { - flex: 1 1 50%; - } - } - - /* SMALL */ - @media (max-width: 576px) { - #dashboard-col-0, - #dashboard-col-1 { - flex: 1 1 100%; - } - - #kpi-wrapper { - flex: 1 1 100%; - } - - #kpi-vitamin-a, - #kpi-iron { - flex: 1 1 50%; - } - } - - .highcharts-figure, - .highcharts-data-table table { - min-width: 310px; - max-width: 800px; - margin: 1em auto; - } - - #container { - height: 400px; - } - - .highcharts-data-table table { - font-family: Verdana, sans-serif; - border-collapse: collapse; - border: 1px solid #ebebeb; - margin: 10px auto; - text-align: center; - width: 100%; - max-width: 500px; - } - - .highcharts-data-table caption { - padding: 1em 0; - font-size: 1.2em; - color: #555; - } - - .highcharts-data-table th { - font-weight: 600; - padding: 0.5em; - } - - .highcharts-data-table td, - .highcharts-data-table th, - .highcharts-data-table caption { - padding: 0.5em; - } - - .highcharts-data-table thead tr, - .highcharts-data-table tr:nth-child(even) { - background: #f8f8f8; - } - - .highcharts-data-table tr:hover { - background: #f1f7ff; - } - - .highcharts-description { - margin: 0.3rem 10px; - } - - h1#title { - padding-top: 10px; - margin: 0; - background-color: #3d3d3d; - text-align: center; - color: #fff; - } - - #kpi-vitamin-a .highcharts-dashboards-component-title::before, - #kpi-iron .highcharts-dashboards-component-title::before { - width: 14px; - height: 14px; - border-radius: 28px; - display: inline-block; - padding: 0; - margin-right: 4px; - background-color: var(--highcharts-color-0); - content: " "; - } - - #kpi-iron .highcharts-dashboards-component-title::before { - background-color: var(--highcharts-color-2); - } - - #kpi-vitamin-a .highcharts-dashboards-component-kpi-subtitle, - #kpi-iron .highcharts-dashboards-component-kpi-subtitle { - margin-top: 10px; - font-size: 1.2em; - color: var(--highcharts-neutral-color-60); - } - - #kpi-vitamin-a .highcharts-dashboards-component-kpi-value, - #kpi-iron .highcharts-dashboards-component-kpi-value { - font-size: 4em; - line-height: 2.4rem; - margin-top: 10px; - color: var(--highcharts-color-0); - } - - #kpi-iron .highcharts-dashboards-component-kpi-value { - color: var(--highcharts-color-2); - } - - #dashboard-col-1 .highcharts-color-0 { - fill: var(--highcharts-color-2); - stroke: var(--highcharts-color-2); - } - - #kpi-vitamin-a .highcharts-dashboards-component-kpi-value::after, - #kpi-iron .highcharts-dashboards-component-kpi-value::after { - content: ""; - display: block; - font-size: 1rem; - } - - .highcharts-plot-line { - stroke-dasharray: 2px; - stroke: var(--highcharts-color-3); - } - - .highcharts-plot-line-label { - fill: var(--highcharts-color-3); - } - - .highcharts-description { - padding: 0 20px; - } - - #kpi-vitamin-a, - #kpi-iron { - flex: 1 1 100%; - height: 205px; - } - - /* LARGE */ - @media (max-width: 1200px) { - #dashboard-col-0, - #dashboard-col-1 { - flex: 1 1 35%; - } - - #kpi-wrapper { - flex: 1 1 30%; - } - - #kpi-vitamin-a, - #kpi-iron { - flex: 1 1 100%; - } - } - - /* MEDIUM */ - @media (max-width: 992px) { - #dashboard-col-0, - #dashboard-col-1 { - flex: 1 1 50%; - } - - #kpi-wrapper { - flex: 1 1 100%; - } - - #kpi-vitamin-a, - #kpi-iron { - flex: 1 1 50%; - } - } - - /* SMALL */ - @media (max-width: 576px) { - #dashboard-col-0, - #dashboard-col-1 { - flex: 1 1 100%; - } - - #kpi-wrapper { - flex: 1 1 100%; - } - - #kpi-vitamin-a, - #kpi-iron { - flex: 1 1 50%; - } + font-size: 16px; } .resumen-grid { @@ -580,15 +73,14 @@ .sub-columns { - /* Cambiamos de grid a flex para que cada div se distribuya correctamente */ display: flex !important; justify-content: space-between; - gap: 1rem; /* espacio entre Venta y Utilidad */ + gap: 1rem; } .sub-columns > div { - flex: 1 1 0; /* igual ancho para cada uno, y que puedan encoger */ - min-width: 0; /* PERMITIR que el contenido se encoja y rompa línea si es muy largo */ + flex: 1 1 0; + min-width: 0; } .sub-header { @@ -605,13 +97,7 @@ border-left: none; } - .row.gx-4 { - column-gap: 1.5rem; - } - .row.gy-4 { - row-gap: 1.5rem; - } .dashboard-stat { padding: 1.5rem; @@ -619,7 +105,7 @@ color: white; } - /* Estilo base para todos los GridViews */ + .custom-grid { -webkit-text-size-adjust: 100%; line-height: 1.42857; @@ -637,19 +123,19 @@ border-collapse: collapse; } - /* Para que los encabezados capitalicen sólo la primera letra */ + .custom-grid th { text-transform: capitalize; } .custom-grid th, .custom-grid td { - border: none !important; /* Quita todas las líneas */ + border: none !important; } - /* Opcional: quita cualquier padding extra en encabezados */ + .custom-grid thead th { padding: 2px !important; } @@ -664,13 +150,13 @@ - /* Línea gruesa bajo los títulos */ + .custom-grid thead th, .resumen-grid thead th { border-bottom: 2px solid #dee2e6 !important; } - /* Línea fina entre filas de datos */ + .custom-grid tbody tr + tr td, .resumen-grid tbody tr + tr td { border-top: 1px solid #dee2e6 !important; @@ -682,49 +168,39 @@ text-transform: capitalize !important; } - /* Evita overflow horizontal */ + .custom-grid { table-layout: fixed; } - /* Envuelve texto largo */ + .custom-grid td, .custom-grid th { white-space: normal !important; word-wrap: break-word !important; } - /* Habilita scrolling horizontal si aún se desborda */ - .table-responsive { - overflow-x: auto; - } - .custom-grid, .resumen-grid { font-size: 8px !important; } - /* Reduce el padding de celdas encabezado/datos a 4px */ + .custom-grid th, .custom-grid td { padding: 2px !important; } - /* Define alto fijo y padding reducido en el contenedor scrollable */ - .portlet-body.table-responsive { - padding: 2px !important; - max-height: 170px !important; - overflow-y: auto; /* si necesitas scroll vertical */ - } + .portlet { height: 200px !important; } .row.gx-4.gy-4 { - align-items: flex-start; /* Alinea siempre los portlets por arriba */ + align-items: flex-start; } - /* 1) Padding global reducido a 4px */ + .row, .portlet, .portlet-title, @@ -732,19 +208,14 @@ .dashboard-stat, .custom-grid, .resumen-grid - .highcharts-container, /* Highcharts interna */ - #divVentasMensuales /* tu contenedor de gráfica */ { + .highcharts-container, + #divVentasMensuales { padding: 2px !important; } - /* 2) Altura máxima de 250px para todos los contenedores scrollables */ - .portlet-body.table-responsive { - max-height: 170px !important; - overflow-y: auto; /* scroll interno si supera 250px */ - overflow-x: hidden; - } - /* ——— Resumen‑grid idéntico a custom‑grid ——— */ + + .resumen-grid, .custom-grid { border-collapse: collapse; @@ -755,27 +226,17 @@ .resumen-grid th, .resumen-grid td { - /* mismo borde inferior */ border: none !important; padding: 2px !important; - /* mismo wrapping */ white-space: normal !important; word-wrap: break-word !important; } - /* ——— Wrapper scrollable idéntico ——— */ - .portlet-body.table-responsive { - /* padding interior reducido */ - padding: 2px !important; - /* misma altura máxima */ - max-height: 170px !important; - overflow-y: auto; - overflow-x: hidden; - } - /* ajustar los sub‑contenedores de “Utilidad” */ + + #gvResumenSucursales .sub-columns, #gvResumenSucursales .sub-columns > div { padding: 2px !important; @@ -787,40 +248,34 @@ .custom-grid th, .resumen-grid td, .resumen-grid th { - padding: 2px !important; /* Padding uniforme de 2px */ + padding: 2px !important; vertical-align: middle !important; } - .portlet-body.table-responsive { - height: 170px !important; /* Altura exacta */ - overflow-y: auto !important; /* Scroll vertical si hay overflow */ - padding: 2px !important; /* Padding interno reducido */ + .portlet-body { + padding: 2px !important; + max-height: 170px !important; } + #gvResumenSucursales .sub-data { - padding: 2px !important; /* Padding igual al resto */ + padding: 2px !important; border-left: 1px solid #dee2e6 !important; } - /* Eliminar padding extra en sub-columnas */ + #gvResumenSucursales .sub-columns > div { padding: 0 !important; margin: 0 !important; } - /*---------------------------------------- - ANCHO DE COLUMNAS DE IMPORTES (AÑO, MES, ETC) ------------------------------------------*/ - - /* Ancho libre para primeras columnas (Sucursal, Vendedor, etc.) */ .custom-grid td:first-child, .custom-grid th:first-child { width: auto !important; min-width: auto !important; max-width: none !important; - } .custom-grid td:nth-child(2), @@ -834,7 +289,7 @@ padding-right: 8px !important; } - /* Alinear encabezados de columnas numéricas */ + .custom-grid th:nth-child(2), .custom-grid th:nth-child(3), .custom-grid th:nth-child(4), @@ -842,7 +297,7 @@ text-align: center !important; } - /* Asegurar ancho libre para primera columna */ + .custom-grid td:first-child, .custom-grid th:first-child { width: auto !important; @@ -851,26 +306,26 @@ } .portlet-title { - margin-bottom: 0px !important; /* Reducir espacio inferior */ - padding: 2px 0 !important; /* Padding vertical mínimo */ + margin-bottom: 0px !important; + padding: 2px 0 !important; } .portlet-title .caption-subject { - font-size: 14px !important; /* Tamaño de fuente compacto */ + font-size: 14px !important; } - /* 1) Restauramos la posición natural de la tabla */ + .portlet.light > .portlet-body.table-responsive { padding-top: 0 !important; margin-top: -16px !important; } - /* 2) Dibujamos la línea separadora justo después del título */ + .portlet.light > .portlet-title { - position: relative; /* para el pseudo-elemento */ - margin-bottom: 0 !important; /* sin espacios extra */ + position: relative; + margin-bottom: 0 !important; padding-bottom: 0 !important; } @@ -880,8 +335,8 @@ bottom: 14px; left: 0; width: 100%; - height: 2px; /* grosor de la línea */ - background-color: #dee2e6; /* mismo color que tus tablas */ + height: 2px; + background-color: #dee2e6; pointer-events: none; z-index: 1; } @@ -891,6 +346,38 @@ margin-bottom: 0 !important; padding-bottom: 0 !important; } + + + .kpi-titulo { + font-size: 14px; + font-weight: bold; + text-align: left; + margin-bottom: 10px; + padding-left: 10px; + position: relative; + padding-right: 180px; + line-height: 1.3; + } + + .kpi-valor { + font-size: 50px; + text-align: center; + font-weight: bold; + line-height: 1.2; + margin: 15px 0; + } + + .divContainerads { + + max-height: 170px; + +} + /* +.gvMaxHeight{ + max-height: 160px; + overflow: auto; +} +*/ @@ -914,14 +401,13 @@ Ingresos Recuperación Compras - Gasto - Cuentas por Pagar - Inventario - Utilidad - Impuestos - CRM - RH - AF + Cuentas por Pagar + Inventario + Utilidad + Impuestos + CRM + RH + AF @@ -930,298 +416,419 @@ -
-
-
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ + Utilidad +
+
+
+ + + + + + + + + + + + +
+
+
+ +
+
+
+ +
+ +
+ +
+
+
+
+ Ventas por sucursal +
+
+
+ + + + + + + + + + +
+
+
+ +
+
+
+
+ Ventas por vendedor +
+
+
+ + + + + + + + + +
+
+
+ +
+ +
+ +
+
+
+
+ Ventas por clasificación +
+
+
+ + + + + + + + + +
+
+
+ +
+
+
+
+ Ventas por marca +
+
+
+ + + + + + + + + +
+
+
+
+ +
+
+
+
+
+ Ventas por PS +
+
+
+ + + + + + + + + +
+
+
+ +
+
+
+
+ Ventas por Cliente +
+
+
+ + + + + + + + + +
+
+
+
+ +
+ +
+
+
+
+ Ventas por Proveedor +
+
+
+ + + + + + + + + +
+
+
+
+
+ + + + + + +
+ +
+
+
+ +
+
+
+ +
+ +
+ +
+
+
+ +
+ +
+ +
- - Utilidad + Cartera por Sucursal
-
- - + + Width="100%"> - - - - - - - - -
-
- -
- -
- -
- -
- -
- -
-
-
-
- Ventas por sucursal -
-
-
- - - - <%# Eval("Sucursal") %> - - - <%# Eval("Año", "{0:N2}") %> - - - <%# Eval("Mes", "{0:N2}") %> - - - <%# Eval("Semana", "{0:N2}") %> - - - <%# Eval("Día", "{0:N2}") %> - + + +
- -
+
- Ventas por vendedor + Cartera por Vendedor
-
- + - - <%# Eval("Vendedor") %> - - - - <%# Eval("Año", "{0:N2}") %> - - - <%# Eval("Mes", "{0:N2}") %> - - - <%# Eval("Semana", "{0:N2}") %> - - - <%# Eval("Día", "{0:N2}") %> - + + + +
+
-
+
+ +
- Ventas por clasificación + Recuperación por Sucursal
-
- + - - <%# Eval("Clasificacion") %> - - - - <%# Eval("Año", "{0:N2}") %> - - - <%# Eval("Mes", "{0:N2}") %> - - - <%# Eval("Semana", "{0:N2}") %> - - - <%# Eval("Día", "{0:N2}") %> - + + +
- -
+
- Ventas por marca + Recuperación por Cliente
-
- + - - <%# Eval("Marca") %> - - - - <%# Eval("Año", "{0:N2}") %> - - - <%# Eval("Mes", "{0:N2}") %> - - - <%# Eval("Semana", "{0:N2}") %> - - - <%# Eval("Día", "{0:N2}") %> - + + +
-
+
+ + + + + +
+
- Ventas por PS + Compras por Clasificación
-
- + - - <%# Eval("ProductoServicio") %> - - - - <%# Eval("Año", "{0:N2}") %> - - - <%# Eval("Mes", "{0:N2}") %> - - - <%# Eval("Semana", "{0:N2}") %> - - - <%# Eval("Día", "{0:N2}") %> - + + + + +
-
+
- Ventas por Cliente + Compras por Marca
-
- + - - <%# Eval("NombreCliente") %> - - - - <%# Eval("Año", "{0:N2}") %> - - - <%# Eval("Mes", "{0:N2}") %> - - - <%# Eval("Semana", "{0:N2}") %> - - - <%# Eval("Día", "{0:N2}") %> - - - -
-
-
- -
-
-
-
- Ventas por Proveedor -
-
-
- - - - <%# Eval("NombreProveedor") %> - - - - <%# Eval("Año", "{0:N2}") %> - - - <%# Eval("Mes", "{0:N2}") %> - - - <%# Eval("Semana", "{0:N2}") %> - - - <%# Eval("Día", "{0:N2}") %> - + + + + +
@@ -1229,22 +836,175 @@
- +
+
+
+
+
+ Compras por PS +
+
+
+ + + + + + + + + +
+
+
- - - - - - - - - +
+
+
+
+ Compras por Proveedor +
+
+
+ + + + + + + + + +
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ CxP por Sucursal +
+
+
+ + + + + + + + +
+
+
+ +
+
+
+
+ CxP por Proveedor +
+
+
+ + + + + + + + +
+
+
+
+ +
+ +
+
+
+
+ Pagos por Sucursal +
+
+
+ + + + + + + +
+
+
+ +
+
+
+
+ Pagos por Proveedor +
+
+
+ + + + + + + +
+
+
+ +
diff --git a/General/Dashboard.aspx.vb b/General/Dashboard.aspx.vb index 21ba073fa..b3f4a34bc 100644 --- a/General/Dashboard.aspx.vb +++ b/General/Dashboard.aspx.vb @@ -25,8 +25,7 @@ Partial Class General_Dashboard End Sub Private Sub BindDropdown() - ' Actualmente las opciones están definidas en el markup - ' Aquí podrías cargar dinámicamente si lo deseas en el futuro + End Sub Public Overrides Sub VerifyRenderingInServerForm(control As Control) @@ -49,7 +48,7 @@ Partial Class General_Dashboard Select Case opcion Case 1 ' Ingresos - ProcesarDatosIndicadores(ds.Tables("Indicadores")) + IndicadoresIngresos(ds.Tables("Indicadores")) CargarResumenSucursales(ds.Tables("DetalleDia")) CargarVentasMensuales(ds.Tables("DetalleAnualesMes")) @@ -63,79 +62,88 @@ Partial Class General_Dashboard Case 2 ' Recuperación - ' TODO: Implementar carga de indicadores, gráficas y grids para Recuperación + IndicadoresRecuperacionCxc(ds.Tables("CarteraPorSubproyecto"), ds.Tables("RecuperacionPorSubproyecto")) + + CargarCarteraPorSucursal(ds.Tables("CarteraPorSubproyecto")) + CargarCarteraPorVendedor(ds.Tables("CarteraPorVendedor")) + CargarRecuperacionPorSucursal(ds.Tables("RecuperacionPorSubproyecto")) + CargarRecuperacionPorCliente(ds.Tables("RecuperacionPorCliente")) + CargarCxCMensual(ds.Tables("CxCMensual")) Case 3 ' Compras - ' TODO: Implementar carga para Compras + CargarComprasPorClasificacion(ds.Tables("ComprasPorClasificación")) + CargarComprasPorMarca(ds.Tables("ComprasPorMarca")) + CargarComprasPorPS(ds.Tables("ComprasPorPS")) + CargarComprasPorProveedor(ds.Tables("ComprasPorProveedor")) - Case 4 ' Gasto - ' TODO: Implementar carga para Gasto + Case 4 ' Cuentas por Pagar + IndicadoresCxP(ds.Tables("CxPPorSubproyecto"), ds.Tables("PagosPorSubproyecto")) - Case 5 ' Cuentas por Pagar - ' TODO: Implementar carga para CxP + ' Cargar GridViews + CargarCxPPorSucursal(ds.Tables("CxPPorSubproyecto")) + CargarCxPPorProveedor(ds.Tables("CxPPorProveedor")) + CargarPagosPorSucursal(ds.Tables("PagosPorSubproyecto")) + CargarPagosPorProveedor(ds.Tables("PagosPorProveedor")) + CargarCxPMensual(ds.Tables("CxPMensual")) - Case 6 ' Inventario + Case 5 ' Inventario ' TODO: Implementar carga para Inventario - Case 7 ' Utilidad + Case 6 ' Utilidad ' TODO: Implementar carga para Utilidad - Case 8 ' Impuestos + Case 7 ' Impuestos ' TODO: Implementar carga para Impuestos - Case 9 ' CRM + Case 8 ' CRM ' TODO: Implementar carga para CRM - Case 10 ' RH + Case 9 ' RH ' TODO: Implementar carga para RH - Case 11 ' AF + Case 10 ' AF ' TODO: Implementar carga para AF End Select End Sub Protected Sub LnkExportarPDF_Click(sender As Object, e As EventArgs) Handles LnkExportarPDF.Click - DivIndicadoresLeft.InnerHtml &= DivIndicadoresRight.InnerHtml - DivIndicadoresRight.InnerHtml = String.Empty + DivIndicadoresIngresosLeft.InnerHtml &= DivIndicadoresIngresosRight.InnerHtml + DivIndicadoresIngresosRight.InnerHtml = String.Empty pnlDashboard.GenerarReportePDF("Reporte Resumen de Indicadores") End Sub - - Private Sub CargarIndicadores(dt As DataTable) - If dt IsNot Nothing AndAlso dt.Rows.Count > 0 Then - ProcesarDatosIndicadores(dt) - End If - End Sub - - Private Sub ProcesarDatosIndicadores(dt As DataTable) + Private Sub IndicadoresIngresos(dt As DataTable) If dt Is Nothing OrElse dt.Rows.Count = 0 Then Return End If Dim dr As DataRow = dt.Rows(0) - ' Indicador Año - MuestraIndicador(DivIndicadoresLeft, "blue", "fa fa-calendar", "Año Actual", dr("AcumAño").ToString.FormatoNumero(0), "#", "Acumulado", + DivIndicadoresIngresosLeft.InnerHtml = String.Empty + DivIndicadoresIngresosRight.InnerHtml = String.Empty + + ' Indicador Año + MuestraIndicador(DivIndicadoresIngresosLeft, "blue", "fa fa-calendar", "Año Actual", dr("AcumAño").ToString.FormatoNumero(0), "#", "Acumulado", dr("AcumAñoPromedio").ToString.FormatoNumero(0), "#", "Ticket Promedio", dr("AcumAñoContado").ToString.FormatoNumero(0), "#", "Contado", dr("AcumAñoCredito").ToString.FormatoNumero(0), "#", "Crédito") - ' Indicador Mes - MuestraIndicador(DivIndicadoresLeft, "green", "fa fa-calendar-check-o", "Mes Actual", + ' Indicador Mes + MuestraIndicador(DivIndicadoresIngresosLeft, "green", "fa fa-calendar-check-o", "Mes Actual", dr("AcumMes").ToString.FormatoNumero(0), "#", "Acumulado", dr("AcumMesPromedio").ToString.FormatoNumero(0), "#", "Ticket Promedio", dr("AcumMesContado").ToString.FormatoNumero(0), "#", "Contado", dr("AcumMesCredito").ToString.FormatoNumero(0), "#", "Crédito") - ' Indicador Semana - MuestraIndicador(DivIndicadoresRight, "purple", "fa fa-calendar-minus-o", "Semana Actual", + ' Indicador Semana + MuestraIndicador(DivIndicadoresIngresosRight, "purple", "fa fa-calendar-minus-o", "Semana Actual", dr("AcumSemana").ToString.FormatoNumero(0), "#", "Acumulado", dr("AcumSemanaPromedio").ToString.FormatoNumero(0), "#", "Ticket Promedio", dr("AcumSemanaContado").ToString.FormatoNumero(0), "#", "Contado", dr("AcumSemanaCredito").ToString.FormatoNumero(0), "#", "Crédito") - MuestraIndicador(DivIndicadoresRight, "red", "fa fa-sun-o", "Hoy", + MuestraIndicador(DivIndicadoresIngresosRight, "red", "fa fa-sun-o", "Hoy", "0".FormatoNumero(0), "#", "Acumulado", "0".FormatoNumero(0), "#", "Ticket Promedio", "0".FormatoNumero(0), "#", "Contado", @@ -143,6 +151,133 @@ Partial Class General_Dashboard End Sub + Private Sub IndicadoresRecuperacionCxc(dtCartera As DataTable, dtRecuperacion As DataTable) + If dtCartera Is Nothing OrElse dtCartera.Rows.Count = 0 Then Return + If dtRecuperacion Is Nothing OrElse dtRecuperacion.Rows.Count = 0 Then Return + + ' Calcular totales para Cartera Vencida + Dim totalVencido As Decimal = 0 + Dim totalPorVencer As Decimal = 0 + Dim totalCxC As Decimal = 0 + Dim maxRotacion As Decimal = 0 + + For Each row As DataRow In dtCartera.Rows + totalVencido += If(IsDBNull(row("Vencido")), 0D, row("Vencido")).ToString.ToDecimal() + totalPorVencer += If(IsDBNull(row("Por Vencer")), 0D, row("Por Vencer")).ToString.ToDecimal() + If dtCartera.Columns.Contains("RotacionCartera") AndAlso Not IsDBNull(row("RotacionCartera")) Then + Dim rotacion = Convert.ToDecimal(row("RotacionCartera")) + If rotacion > maxRotacion Then maxRotacion = rotacion + End If + + Next + + totalCxC = totalVencido + totalPorVencer + + ' Calcular total para Recuperación + Dim totalRecuperado As Decimal = 0 + For Each row As DataRow In dtRecuperacion.Rows + totalRecuperado += If(IsDBNull(row("Recuperado")), 0D, row("Recuperado")).ToString.ToDecimal() + Next + + DivIndicadoresRecuperacionLeft.InnerHtml = "" + DivIndicadoresRecuperacionRight.InnerHtml = "" + + ' Indicador Cartera Vencida + MuestraIndicador(DivIndicadoresRecuperacionLeft, "blue", "fa fa-calendar", "Cartera Vencida", + totalCxC.ToString.FormatoNumero(0), "#", "CxC", + totalVencido.ToString.FormatoNumero(0), "#", "Vencido", + totalPorVencer.ToString.FormatoNumero(0), "#", "Por Vencer") + + 'Indicador KPI Recuperación + MuestraIndicadorKPI(DivIndicadoresRecuperacionLeft, "purple", "fa fa-usd", + "Recuperación", + totalRecuperado.ToString.FormatoNumero(0), + "#", + "Recuperación") + + Dim factorRecuperacion As Decimal = 0 + If totalCxC > 0 Then + factorRecuperacion = (totalRecuperado / totalCxC) + End If + + MuestraIndicadorKPI(DivIndicadoresRecuperacionRight, "green", "fa fa-fa-percent", + "Factor Recuperación", + factorRecuperacion.ToString("N2") & "%", + "#", + "Porcentaje de cartera recuperada") + + MuestraIndicadorKPI(DivIndicadoresRecuperacionRight, "red", "fa fa-refresh", + "Rotación Cartera", + maxRotacion.ToString("N1"), + "#", + "Máxima rotación de cartera entre sucursales") + + End Sub + + Private Sub IndicadoresCxP(dtCxP As DataTable, dtPagos As DataTable) + If dtCxP Is Nothing OrElse dtCxP.Rows.Count = 0 Then Return + If dtPagos Is Nothing OrElse dtPagos.Rows.Count = 0 Then Return + + ' Calcular totales para CxP + Dim totalVencido As Decimal = 0 + Dim totalPorVencer As Decimal = 0 + Dim totalCxP As Decimal = 0 + Dim maxRotacion As Decimal = 0 + + For Each row As DataRow In dtCxP.Rows + totalVencido += Convert.ToDecimal(If(IsDBNull(row("Vencido")), 0D, row("Vencido"))) + totalPorVencer += Convert.ToDecimal(If(IsDBNull(row("Por Vencer")), 0D, row("Por Vencer"))) + + ' Obtener rotación máxima + If dtCxP.Columns.Contains("RotacionCartera") AndAlso Not IsDBNull(row("RotacionCartera")) Then + Dim rotacion = Convert.ToDecimal(row("RotacionCartera")) + If rotacion > maxRotacion Then maxRotacion = rotacion + End If + Next + totalCxP = totalVencido + totalPorVencer + + ' Calcular total pagado + Dim totalPagado As Decimal = 0 + For Each row As DataRow In dtPagos.Rows + totalPagado += Convert.ToDecimal(If(IsDBNull(row("Recuperado")), 0D, row("Recuperado"))) + Next + + DivIndicadoresCxPLeft.InnerHtml = "" + DivIndicadoresRecuperacionRight.InnerHtml = "" + + ' Indicador CxP + MuestraIndicador(DivIndicadoresCxPLeft, "blue", "fa fa-calendar", "Cuentas por Pagar", + totalCxP.ToString.FormatoNumero(0), "#", "Total CxP", + totalVencido.ToString.FormatoNumero(0), "#", "Vencido", + totalPorVencer.ToString.FormatoNumero(0), "#", "Por Vencer") + + ' KPI Pagado + MuestraIndicadorKPI(DivIndicadoresCxPLeft, "purple", "fa fa-usd", + "Total Pagado", + totalPagado.ToString.FormatoNumero(0), + "#", + "Total pagado a proveedores") + + ' KPI Factor CxP vs Pagado + Dim factorPago As Decimal = 0 + If totalCxP > 0 Then + factorPago = (totalPagado / totalCxP) * 100 + End If + + MuestraIndicadorKPI(DivIndicadoresCxPRight, "green", "fa fa-percent", + "Factor Pago", + factorPago.ToString("N2") & "%", + "#", + "Porcentaje de CxP pagado") + + ' KPI Rotación de Cartera + MuestraIndicadorKPI(DivIndicadoresCxPRight, "red", "fa fa-refresh", + "Rotación CxP", + maxRotacion.ToString("N1") & " días", + "#", + "Máxima rotación de CxP entre sucursales") + End Sub + Private Sub CargarVentasMensuales(dt As DataTable) If dt Is Nothing OrElse dt.Rows.Count = 0 Then Return @@ -480,7 +615,420 @@ Partial Class General_Dashboard gvVentasPorProveedor.DataBind() End Sub + Private Sub CargarCarteraPorSucursal(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then + Return + End If + ' Crear DataTable con estructura necesaria + Dim dtGrid = New DataTable() + dtGrid.Columns.Add("Sucursal", GetType(String)) + dtGrid.Columns.Add("TotalCxC", GetType(Decimal)) + dtGrid.Columns.Add("Vencido", GetType(Decimal)) + dtGrid.Columns.Add("PorVencer", GetType(Decimal)) + + For Each row As DataRow In dt.Rows + Dim vencido = (If(IsDBNull(row("Vencido")), 0D, row("Vencido"))).ToString.ToDecimal() + Dim porVencer = (If(IsDBNull(row("Por Vencer")), 0D, row("Por Vencer"))).ToString.ToDecimal() + Dim totalCxC = vencido + porVencer + + dtGrid.Rows.Add( + row("Sucursal").ToString(), + totalCxC, + vencido, + porVencer + ) + Next + + gvCarteraPorSucursal.DataSource = dtGrid + gvCarteraPorSucursal.DataBind() + End Sub + + Private Sub CargarCarteraPorVendedor(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then + Return + End If + + ' Crear DataTable con estructura necesaria + Dim dtGrid = New DataTable() + dtGrid.Columns.Add("Vendedor", GetType(String)) + dtGrid.Columns.Add("TotalCxC", GetType(Decimal)) + dtGrid.Columns.Add("Vencido", GetType(Decimal)) + dtGrid.Columns.Add("PorVencer", GetType(Decimal)) + + For Each row As DataRow In dt.Rows + Dim vencido = Convert.ToDecimal(If(IsDBNull(row("Vencido")), 0D, row("Vencido"))) + Dim porVencer = Convert.ToDecimal(If(IsDBNull(row("Por Vencer")), 0D, row("Por Vencer"))) + Dim totalCxC = vencido + porVencer + + dtGrid.Rows.Add( + row("Vendedor").ToString(), + totalCxC, + vencido, + porVencer + ) + Next + + gvCarteraPorVendedor.DataSource = dtGrid + gvCarteraPorVendedor.DataBind() + End Sub + + Private Sub CargarCxCMensual(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then + Return + End If + + ' Lista de meses en español + Dim mesesOrdenados As New List(Of String) From { + "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", + "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre" + } + + ' Obtener años distintos de forma segura + Dim años As New List(Of Integer) + For Each row As DataRow In dt.Rows + If Not IsDBNull(row("Año")) Then + Dim añoValue As String = row("Año").ToString() + Dim añoInt As Integer + If Integer.TryParse(añoValue, añoInt) Then + If Not años.Contains(añoInt) Then + años.Add(añoInt) + End If + End If + End If + Next + años = años.OrderBy(Function(y) y).ToList() + + Dim seriesData As New List(Of Dictionary(Of String, Object)) + + For Each año In años + ' Filtrar datos para este año + Dim añoData = dt.AsEnumerable() _ + .Where(Function(r) Not IsDBNull(r("Año")) AndAlso + Integer.TryParse(r("Año").ToString(), Nothing) AndAlso + Convert.ToInt32(r("Año")) = año) _ + .FirstOrDefault() + + If añoData IsNot Nothing Then + Dim dataPoints As New List(Of Object()) + + ' Obtener valores para cada mes + For i = 0 To mesesOrdenados.Count - 1 + Dim mes = mesesOrdenados(i) + If dt.Columns.Contains(mes) Then + Dim valor As Decimal = 0D + If Not IsDBNull(añoData(mes)) Then + ' Conversión segura a decimal + Decimal.TryParse(añoData(mes).ToString(), valor) + End If + dataPoints.Add({i, valor}) + End If + Next + + seriesData.Add(New Dictionary(Of String, Object) From { + {"name", año.ToString}, + {"data", dataPoints}, + {"color", GetColorForYear(años.IndexOf(año))} + }) + End If + Next + + ' Configuración de la gráfica (igual que la de ventas) + Dim chartOptions = New With { + .chart = New With {.type = "line"}, + .title = New With {.text = "CxC Mensual"}, + .xAxis = New With { + .type = "category", + .categories = mesesOrdenados, + .title = New With {.text = "Mes"} + }, + .yAxis = New With { + .title = New With {.text = "Monto CxC (MXN)"}, + .labels = New With {.format = "${value:,.0f}"} + }, + .tooltip = New With { + .headerFormat = "{series.name}
", + .pointFormat = "Mes: {point.category}
CxC: ${point.y:,.2f} MXN" + }, + .plotOptions = New With { + .line = New With { + .dataLabels = New With {.enabled = False}, + .enableMouseTracking = True + } + }, + .series = seriesData + } + + Dim script = $" + Highcharts.chart('divCxCMensual', {JsonConvert.SerializeObject(chartOptions)}); + " + ScriptManager.RegisterStartupScript(Me, Me.GetType(), "graficaCxC", script, True) + End Sub + + Private Sub CargarRecuperacionPorSucursal(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then + Return + End If + + ' Renombrar columna para mejor presentación + If dt.Columns.Contains("P. Días recuperodo vencidos") Then + dt.Columns("P. Días recuperodo vencidos").ColumnName = "PromedioDiasRecuperacion" + End If + + gvRecuperacionPorSucursal.DataSource = dt + gvRecuperacionPorSucursal.DataBind() + End Sub + + Private Sub CargarRecuperacionPorCliente(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then + Return + End If + + ' Crear DataTable con estructura necesaria + Dim dtGrid = New DataTable() + dtGrid.Columns.Add("Cliente", GetType(String)) + dtGrid.Columns.Add("Recuperado", GetType(Decimal)) + dtGrid.Columns.Add("PromedioDiasRecuperacion", GetType(Decimal)) + + For Each row As DataRow In dt.Rows + dtGrid.Rows.Add( + row("Nombre").ToString(), ' Asegurar que la columna se llama "Nombre" + Convert.ToDecimal(If(IsDBNull(row("Recuperado")), 0D, row("Recuperado"))), + Convert.ToDecimal(If(IsDBNull(row("P. Días recuperodo vencidos")), 0D, row("P. Días recuperodo vencidos"))) + ) + Next + + gvRecuperacionPorCliente.DataSource = dtGrid + gvRecuperacionPorCliente.DataBind() + End Sub + + Private Sub CargarComprasPorClasificacion(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then + Return + End If + + gvComprasPorClasificacion.DataSource = dt + gvComprasPorClasificacion.DataBind() + End Sub + + Private Sub CargarComprasPorMarca(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then + Return + End If + + gvComprasPorMarca.DataSource = dt + gvComprasPorMarca.DataBind() + End Sub + + Private Sub CargarComprasPorPS(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then + Return + End If + + gvComprasPorPS.DataSource = dt + gvComprasPorPS.DataBind() + End Sub + + Private Sub CargarComprasPorProveedor(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then + Return + End If + + gvComprasPorProveedor.DataSource = dt + gvComprasPorProveedor.DataBind() + End Sub + + ' Métodos para cargar los GridViews + Private Sub CargarCxPPorSucursal(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then Return + + ' Crear un nuevo DataTable con las columnas necesarias + Dim dtGrid = New DataTable() + dtGrid.Columns.Add("Sucursal", GetType(String)) + dtGrid.Columns.Add("Vencido", GetType(Decimal)) + dtGrid.Columns.Add("PorVencer", GetType(Decimal)) + dtGrid.Columns.Add("Total", GetType(Decimal)) ' Columna Total requerida + + ' Copiar datos y calcular Total + For Each row As DataRow In dt.Rows + Dim vencido = Convert.ToDecimal(If(IsDBNull(row("Vencido")), 0D, row("Vencido"))) + Dim porVencer = Convert.ToDecimal(If(IsDBNull(row("Por Vencer")) OrElse Not dt.Columns.Contains("Por Vencer"), 0D, row("Por Vencer"))) + Dim total = vencido + porVencer + + dtGrid.Rows.Add( + row("Sucursal").ToString(), + vencido, + porVencer, + total + ) + Next + + gvCxPPorSucursal.DataSource = dtGrid + gvCxPPorSucursal.DataBind() + End Sub + + Private Sub CargarCxPPorProveedor(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then Return + + ' Crear un nuevo DataTable con las columnas necesarias + Dim dtGrid = New DataTable() + dtGrid.Columns.Add("RazonSocial", GetType(String)) + dtGrid.Columns.Add("Vencido", GetType(Decimal)) + dtGrid.Columns.Add("PorVencer", GetType(Decimal)) + dtGrid.Columns.Add("Total", GetType(Decimal)) ' Columna Total requerida + + ' Copiar datos y calcular Total + For Each row As DataRow In dt.Rows + Dim vencido = Convert.ToDecimal(If(IsDBNull(row("Vencido")), 0D, row("Vencido"))) + Dim porVencer = Convert.ToDecimal(If(IsDBNull(row("Por Vencer")) OrElse Not dt.Columns.Contains("Por Vencer"), 0D, row("Por Vencer"))) + Dim total = vencido + porVencer + + dtGrid.Rows.Add( + row("RazonSocial").ToString(), + vencido, + porVencer, + total + ) + Next + + gvCxPPorProveedor.DataSource = dtGrid + gvCxPPorProveedor.DataBind() + End Sub + + Private Sub CargarPagosPorSucursal(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then Return + + ' Crear nuevo DataTable con nombres de columnas consistentes + Dim dtGrid = New DataTable() + dtGrid.Columns.Add("Sucursal", GetType(String)) + dtGrid.Columns.Add("Recuperado", GetType(Decimal)) + dtGrid.Columns.Add("PromedioDias", GetType(Decimal)) + + For Each row As DataRow In dt.Rows + dtGrid.Rows.Add( + row("Sucursal").ToString(), + Convert.ToDecimal(If(IsDBNull(row("Recuperado")), 0D, row("Recuperado"))), + Convert.ToDecimal(If(IsDBNull(row("P. Días recuperodo vencidos")), 0D, row("P. Días recuperodo vencidos"))) + ) + Next + + gvPagosPorSucursal.DataSource = dtGrid + gvPagosPorSucursal.DataBind() + End Sub + + Private Sub CargarPagosPorProveedor(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then Return + + ' Crear nuevo DataTable con nombres de columnas consistentes + Dim dtGrid = New DataTable() + dtGrid.Columns.Add("RazonSocial", GetType(String)) + dtGrid.Columns.Add("Recuperado", GetType(Decimal)) + dtGrid.Columns.Add("PromedioDias", GetType(Decimal)) + + For Each row As DataRow In dt.Rows + dtGrid.Rows.Add( + row("RazonSocial").ToString(), + Convert.ToDecimal(If(IsDBNull(row("Recuperado")), 0D, row("Recuperado"))), + Convert.ToDecimal(If(IsDBNull(row("P. Días recuperodo vencidos")), 0D, row("P. Días recuperodo vencidos"))) + ) + Next + + gvPagosPorProveedor.DataSource = dtGrid + gvPagosPorProveedor.DataBind() + End Sub + + Private Sub CargarCxPMensual(dt As DataTable) + If dt Is Nothing OrElse dt.Rows.Count = 0 Then + Return + End If + + ' Lista de meses en español + Dim mesesOrdenados As New List(Of String) From { + "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", + "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre" + } + + ' Obtener años distintos de forma segura + Dim años As New List(Of Integer) + For Each row As DataRow In dt.Rows + If Not IsDBNull(row("Año")) Then + Dim añoValue As String = row("Año").ToString() + Dim añoInt As Integer + If Integer.TryParse(añoValue, añoInt) Then + If Not años.Contains(añoInt) Then + años.Add(añoInt) + End If + End If + End If + Next + años = años.OrderBy(Function(y) y).ToList() + + Dim seriesData As New List(Of Dictionary(Of String, Object)) + + For Each año In años + ' Filtrar datos para este año + Dim añoData = dt.AsEnumerable() _ + .Where(Function(r) Not IsDBNull(r("Año")) AndAlso + Integer.TryParse(r("Año").ToString(), Nothing) AndAlso + Convert.ToInt32(r("Año")) = año) _ + .FirstOrDefault() + + If añoData IsNot Nothing Then + Dim dataPoints As New List(Of Object()) + + ' Obtener valores para cada mes + For i = 0 To mesesOrdenados.Count - 1 + Dim mes = mesesOrdenados(i) + If dt.Columns.Contains(mes) Then + Dim valor As Decimal = 0D + If Not IsDBNull(añoData(mes)) Then + ' Conversión segura a decimal + Decimal.TryParse(añoData(mes).ToString(), valor) + End If + ' Usar el índice del mes (0-11) como posición X + dataPoints.Add({i, valor}) + End If + Next + + seriesData.Add(New Dictionary(Of String, Object) From { + {"name", año.ToString}, + {"data", dataPoints}, + {"color", GetColorForYear(años.IndexOf(año))} + }) + End If + Next + + ' Configuración de la gráfica (mismo estilo que CxC) + Dim chartOptions = New With { + .chart = New With {.type = "line"}, + .title = New With {.text = "Evolución de Cuentas por Pagar (CxP) Mensual"}, + .xAxis = New With { + .type = "category", + .categories = mesesOrdenados, + .title = New With {.text = "Mes"} + }, + .yAxis = New With { + .title = New With {.text = "Monto CxP (MXN)"}, + .labels = New With {.format = "${value:,.0f}"} + }, + .tooltip = New With { + .headerFormat = "{series.name}
", + .pointFormat = "Mes: {point.category}
CxP: ${point.y:,.2f} MXN" + }, + .plotOptions = New With { + .line = New With { + .dataLabels = New With {.enabled = False}, + .enableMouseTracking = True + } + }, + .series = seriesData + } + + Dim script = $" + Highcharts.chart('divCxPMensual', {JsonConvert.SerializeObject(chartOptions)}); + " + ScriptManager.RegisterStartupScript(Me, Me.GetType(), "graficaCxP", script, True) + End Sub Private Function ObtenerMesesOrdenados() As List(Of String) Return CultureInfo.CurrentCulture.DateTimeFormat.MonthNames _ @@ -536,4 +1084,38 @@ Partial Class General_Dashboard DIV.InnerHtml = htmldiv.ToString End Sub + ' Método para mostrar el KPI (Agrégalo en tu clase) + Private Sub MuestraIndicadorKPI(DIV As HtmlGenericControl, Color As String, Icono As String, Titulo As String, Valor As String, CL1 As String, Tooltip As String) + Dim htmldiv As New StringBuilder + + + ' Estructura del contenedor principal + htmldiv.AppendFormat( + "{0}
" & + "
" & ' Contenedor relativo + "
", + DIV.InnerHtml, Color, Icono + ) + + ' Contenido principal + htmldiv.Append("
") + htmldiv.AppendFormat( + "
{1}
", + Tooltip, Titulo + ) + htmldiv.AppendFormat("
{0}
", Valor) + htmldiv.Append("
") + + + htmldiv.AppendFormat( + "" & + "Ver Más", + CL1 + ) + + htmldiv.Append("
") ' Cierre de dashboard-stat y col + + DIV.InnerHtml = htmldiv.ToString + End Sub + End Class diff --git a/Inicio.aspx.vb b/Inicio.aspx.vb index 570085ae2..65749abba 100644 --- a/Inicio.aspx.vb +++ b/Inicio.aspx.vb @@ -1665,7 +1665,7 @@ Partial Class Inicio Dim FechaI As Date = txtFechaInicioSMS.Text.ToDate ' Now.FirstDayOfMonth Dim FechaF As Date = txtFechaFinSMS.Text.ToDate 'Now.LastDayOfMonth - If Gral.GetSMSIndicadores(Opcion, FechaI, FechaF, UsuarioActual.CveUsuario, CveVendedor, CveGerenteVendedor, CveUsuarioTecnico, UsuarioActual.CveEmpresa) Then + If Gral.GetSMSIndicadores(Opcion, FechaI, FechaF, UsuarioActual.CveUsuario, CveVendedor, CveUsuarioTecnico, UsuarioActual.CveEmpresa) Then ds = Gral.DataSetResultados End If '0-ventas totales, 1-comisiones totales, 2-medicos sin consumo, 3-top10medicos, 4-top5marcas, 5-num pacientes diff --git a/OP/OP.aspx b/OP/OP.aspx index dfdc3300f..e173c9a42 100644 --- a/OP/OP.aspx +++ b/OP/OP.aspx @@ -310,18 +310,16 @@ cursor: pointer; } /* Alinear celda y contenido a la derecha */ -.celda-derecha { - text-align: right !important; - -} - -.txt-merma { - /* Ancho reducido al 50% */ - margin-left: 30px !important; /* Mueve el TextBox a la derecha dentro de la celda */ - display: block !important; /* Permite que margin-left:auto funcione */ - /* Alinea el texto interno a la derecha */ -} + .celda-derecha { + text-align: right !important; + } + .txt-merma { + /* Ancho reducido al 50% */ + margin-left: 30px !important; /* Mueve el TextBox a la derecha dentro de la celda */ + display: block !important; /* Permite que margin-left:auto funcione */ + /* Alinea el texto interno a la derecha */ + } @@ -364,16 +362,16 @@
- +
- +
@@ -428,7 +426,7 @@ ControlToValidate="TxtDescripcion" ErrorMessage="Descripción requerida" Display="None" - CssClass="text-danger" + CssClass="text-danger" ValidationGroup="GrpGuardar" />
@@ -437,7 +435,7 @@
- +
@@ -471,13 +469,13 @@
- -
- -
+ +
+ +
+ onchange="validarCantidad(this)" + ItemStyle-HorizontalAlign="Right" /> @@ -872,18 +870,17 @@ CssClass="table table-striped table-hover" GridLines="None" Width="100%"> - + + onchange="validarCantidad(this)" /> @@ -947,15 +944,14 @@ + data-original='<%# Eval("Cantidad")%>' /> - + diff --git a/OP/OP.aspx.vb b/OP/OP.aspx.vb index 7e1cee9f6..c0166cd12 100644 --- a/OP/OP.aspx.vb +++ b/OP/OP.aspx.vb @@ -125,6 +125,7 @@ Partial Class OP_OP BtnCancelarOP.Visible = (CveOP > 0 AndAlso TipoEstatus <> 3 AndAlso TipoEstatus <> 4) End Sub +#Region "Metodos" Private Sub CreaPS() DtPS = New DataTable() DtPS.Columns.Add("Tipo", GetType(String)) ' Producto, MP, MO, CI @@ -247,7 +248,9 @@ Partial Class OP_OP UpBtns.Update() UpModalCancelar.Update() End Sub +#End Region +#Region "Cargar" Private Sub CargaDatos() If CveOP > 0 Then Dim ObjOP As New ClsOP() @@ -367,7 +370,9 @@ Partial Class OP_OP DdlPS.ddl.Items.Insert(0, New ListItem("Seleccione el Producto", "-1")) End If End Sub +#End Region +#Region "Producto" Protected Sub BtnAgregarProducto_Click(sender As Object, e As EventArgs) Handles BtnAgregarProducto.Click If DdlPS.SelectedValue = "-1" Then Exit Sub @@ -480,33 +485,16 @@ Partial Class OP_OP Next End Sub + Protected Sub GvProductos_RowDataBound(sender As Object, e As GridViewRowEventArgs) Handles GvProductos.RowDataBound If e.Row.RowType = DataControlRowType.DataRow Then Dim btnEliminar As LinkButton = DirectCast(e.Row.FindControl("BtnEliminar"), LinkButton) btnEliminar.Visible = (TipoEstatus <> 3) End If End Sub +#End Region - Protected Sub LnkSolicitarMP_Click(sender As Object, e As EventArgs) - ' Cargar almacenes - DdlAlmacenOrigenMP.DataSource = GlobalDTAlmacen(UsuarioActual.CveEmpresa).FiltraParametros("CveTipoAlmacen = 1") - DdlAlmacenOrigenMP.DataBind() - DdlAlmacenDestinoMP.DataSource = GlobalDTAlmacen(UsuarioActual.CveEmpresa).FiltraParametros("CveTipoAlmacen = 28") - DdlAlmacenDestinoMP.DataBind() - - GvMPAgregados.DataKeyNames = New String() {"CvePSMateriaPrima", "CveUMMateriaPrima"} - - ' Filtrar MP no surtida - Dim DtMPFaltante As DataTable = DtMP.FiltraParametros("Surtido = 0").Copy() - GvMPAgregados.DataSource = DtMPFaltante - GvMPAgregados.DataBind() - - - ' Mostrar modal - RegistraScript("$('#DVModalSolicitarMP').modal('show');") - UpModalMP.Update() - End Sub - +#Region "Simular MP" Protected Sub BtnSimularMP_Click(sender As Object, e As EventArgs) If CveOP <> -1 Then Exit Sub If DdlPS.SelectedValue = "-1" Then Exit Sub @@ -664,7 +652,28 @@ Partial Class OP_OP Return ObjOP.DtResultados.Copy() End Function +#End Region +#Region "Solicitar MP" + Protected Sub LnkSolicitarMP_Click(sender As Object, e As EventArgs) + ' Cargar almacenes + DdlAlmacenOrigenMP.DataSource = GlobalDTAlmacen(UsuarioActual.CveEmpresa).FiltraParametros("CveTipoAlmacen = 1") + DdlAlmacenOrigenMP.DataBind() + DdlAlmacenDestinoMP.DataSource = GlobalDTAlmacen(UsuarioActual.CveEmpresa).FiltraParametros("CveTipoAlmacen = 28") + DdlAlmacenDestinoMP.DataBind() + + GvMPAgregados.DataKeyNames = New String() {"CvePSMateriaPrima", "CveUMMateriaPrima"} + + ' Filtrar MP no surtida + Dim DtMPFaltante As DataTable = DtMP.FiltraParametros("Surtido = 0").Copy() + GvMPAgregados.DataSource = DtMPFaltante + GvMPAgregados.DataBind() + + + ' Mostrar modal + RegistraScript("$('#DVModalSolicitarMP').modal('show');") + UpModalMP.Update() + End Sub Protected Sub BtnGuardarSolicitudMP_Click(sender As Object, e As EventArgs) Dim CveTraspasoSolicitud As Integer = -1 @@ -772,6 +781,9 @@ Partial Class OP_OP ObjInventario.Dispose() End Sub +#End Region + +#Region "Guardar OP" Protected Sub BtnGuardarOP_Click(sender As Object, e As EventArgs) ' Validaciones básicas @@ -930,6 +942,8 @@ Partial Class OP_OP End If End Sub +#End Region + #Region "Cancelar" Protected Sub BtnCancelarOP_Click(sender As Object, e As EventArgs) ' Cargar almacenes