Ajustesgis parte 1

This commit is contained in:
Jorge Luis caudillo esparza
2026-08-18 16:37:29 -07:00
parent 4e80ecca77
commit 3c0b18f474
8 changed files with 968 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
using System.IO;
using SIG.Web.AppCode.Models.Fraccionamientos;
namespace SIG.Web.AppCode.Contracts.Fraccionamientos
{
public interface IFraccionamientoGisFileStore
{
string GetPhysicalPath(FraccionamientoGisContextModel context);
bool Exists(FraccionamientoGisContextModel context);
void Save(FraccionamientoGisContextModel context, Stream source);
Stream OpenRead(FraccionamientoGisContextModel context);
}
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Models.Fraccionamientos;
namespace SIG.Web.AppCode.Contracts.Fraccionamientos
{
public interface IFraccionamientoGisRepository
{
DbResult<FraccionamientoGisContextModel> GetContext(
int stageId,
int executingUser,
string domainName);
DbResult<List<FraccionamientoGisLotLinkModel>> ListLinks(
int stageId,
int executingUser,
string domainName);
DbResult<bool> SaveLinks(
int stageId,
IList<FraccionamientoGisLotLinkModel> links,
int executingUser,
string domainName);
}
}

View File

@@ -0,0 +1,19 @@
namespace SIG.Web.AppCode.Models.Fraccionamientos
{
public sealed class FraccionamientoGisContextModel
{
public int CveCliente { get; set; }
public int CveFraccionamiento { get; set; }
public int CveFraccionamientoEtapa { get; set; }
public string Fraccionamiento { get; set; }
public string Etapa { get; set; }
}
public sealed class FraccionamientoGisLotLinkModel
{
public int CveFraccionamientoLote { get; set; }
public string Nombre { get; set; }
public string GISPoligonoClave { get; set; }
public bool EsActivo { get; set; }
}
}

View File

@@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Data;
using SIG.Web.AppCode.Contracts.Database;
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Utilities;
namespace SIG.Web.AppCode.Repositories.Database
{
public abstract class RepositoryBase
{
private readonly IDbExecutor _db;
protected RepositoryBase(IDbExecutor db)
{
if (db == null)
{
throw new ArgumentNullException("db");
}
_db = db;
}
protected DbResult<List<T>> QueryList<T>(
string procedure,
int user,
string domain,
Action<IDbCommand> parameters)
where T : class, new()
{
return _db.Execute(domain, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, procedure, user);
if (parameters != null)
{
parameters(command);
}
List<T> value;
using (IDataReader reader = command.ExecuteReader())
{
value = reader.ToList<T>();
}
StoredProcedureCommand.EnsureSuccess(command);
return value;
}
});
}
protected DbResult<T> QueryOne<T>(
string procedure,
int user,
string domain,
Action<IDbCommand> parameters)
where T : class, new()
{
return _db.Execute(domain, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, procedure, user);
if (parameters != null)
{
parameters(command);
}
T value;
using (IDataReader reader = command.ExecuteReader())
{
value = reader.ToObject<T>();
}
StoredProcedureCommand.EnsureSuccess(command);
return value;
}
});
}
protected DbResult<int> QueryScalarId(
string procedure,
int user,
string domain,
Action<IDbCommand> parameters)
{
return _db.Execute(domain, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, procedure, user);
if (parameters != null)
{
parameters(command);
}
int value;
using (IDataReader reader = command.ExecuteReader())
{
value = reader.Read()
? Convert.ToInt32(reader.GetValue(0))
: 0;
}
StoredProcedureCommand.EnsureSuccess(command);
return value;
}
});
}
protected DbResult<bool> Execute(
string procedure,
int user,
string domain,
Action<IDbCommand> parameters)
{
return _db.Execute(domain, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, procedure, user);
if (parameters != null)
{
parameters(command);
}
command.ExecuteNonQuery();
StoredProcedureCommand.EnsureSuccess(command);
return true;
}
});
}
}
}

View File

@@ -0,0 +1,93 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Xml.Linq;
using SIG.Web.AppCode.Contracts.Database;
using SIG.Web.AppCode.Contracts.Fraccionamientos;
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Models.Fraccionamientos;
using SIG.Web.AppCode.Repositories.Database;
namespace SIG.Web.AppCode.Repositories.Fraccionamientos
{
public sealed class FraccionamientoGisRepository
: RepositoryBase, IFraccionamientoGisRepository
{
public FraccionamientoGisRepository(IDbExecutor db)
: base(db)
{
}
public DbResult<FraccionamientoGisContextModel> GetContext(
int stageId,
int executingUser,
string domainName)
{
return QueryOne<FraccionamientoGisContextModel>(
"dbo.PA_Fraccionamientos_EtapaGISContextGet",
executingUser,
domainName,
command => StoredProcedureCommand.AddInput(
command,
"@CveFraccionamientoEtapa",
stageId,
DbType.Int32));
}
public DbResult<List<FraccionamientoGisLotLinkModel>> ListLinks(
int stageId,
int executingUser,
string domainName)
{
return QueryList<FraccionamientoGisLotLinkModel>(
"dbo.PA_Fraccionamientos_EtapaGISVinculosGet",
executingUser,
domainName,
command => StoredProcedureCommand.AddInput(
command,
"@CveFraccionamientoEtapa",
stageId,
DbType.Int32));
}
public DbResult<bool> SaveLinks(
int stageId,
IList<FraccionamientoGisLotLinkModel> links,
int executingUser,
string domainName)
{
IList<FraccionamientoGisLotLinkModel> safeLinks =
links ?? new List<FraccionamientoGisLotLinkModel>();
XElement linksXml = new XElement(
"Vinculos",
safeLinks.Select(link =>
new XElement(
"Vinculo",
new XAttribute(
"CveFraccionamientoLote",
link.CveFraccionamientoLote),
new XAttribute(
"GISPoligonoClave",
link.GISPoligonoClave ?? string.Empty))));
return Execute(
"dbo.PA_Fraccionamientos_EtapaGISVinculosSave",
executingUser,
domainName,
command =>
{
StoredProcedureCommand.AddInput(
command,
"@CveFraccionamientoEtapa",
stageId,
DbType.Int32);
StoredProcedureCommand.AddInput(
command,
"@VinculosXml",
linksXml.ToString(SaveOptions.DisableFormatting),
DbType.Xml);
});
}
}
}

View File

@@ -0,0 +1,89 @@
using System;
using System.IO;
using System.Web.Hosting;
using SIG.Web.AppCode.Contracts.Fraccionamientos;
using SIG.Web.AppCode.Models.Fraccionamientos;
namespace SIG.Web.AppCode.Services.Fraccionamientos
{
public sealed class FraccionamientoGisFileStore : IFraccionamientoGisFileStore
{
public string GetPhysicalPath(FraccionamientoGisContextModel context)
{
ValidateContext(context);
string relativeDirectory = string.Format(
"~/Documentos/Clientes/{0}/",
context.CveCliente);
string directory = HostingEnvironment.MapPath(relativeDirectory);
if (string.IsNullOrWhiteSpace(directory))
{
throw new InvalidOperationException(
"No fue posible resolver el directorio de documentos GIS.");
}
return Path.Combine(
directory,
string.Format(
"gis-{0}-{1}.gis",
context.CveFraccionamiento,
context.CveFraccionamientoEtapa));
}
public bool Exists(FraccionamientoGisContextModel context)
{
return File.Exists(GetPhysicalPath(context));
}
public void Save(FraccionamientoGisContextModel context, Stream source)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
string path = GetPhysicalPath(context);
string directory = Path.GetDirectoryName(path);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
using (FileStream destination = new FileStream(
path,
FileMode.Create,
FileAccess.Write,
FileShare.None))
{
source.CopyTo(destination);
}
}
public Stream OpenRead(FraccionamientoGisContextModel context)
{
return new FileStream(
GetPhysicalPath(context),
FileMode.Open,
FileAccess.Read,
FileShare.Read);
}
private static void ValidateContext(FraccionamientoGisContextModel context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (context.CveCliente <= 0 ||
context.CveFraccionamiento <= 0 ||
context.CveFraccionamientoEtapa <= 0)
{
throw new InvalidOperationException(
"El contexto del archivo GIS no contiene claves válidas.");
}
}
}
}

View File

@@ -0,0 +1,576 @@
(function (window, $, undefined) {
'use strict';
var api = window.FraccionamientoGIS = window.FraccionamientoGIS || {};
var current = null;
function normalizeOptions(options) {
return $.extend({
fileInputId: 'TxtGIS',
mapId: 'MapGIS',
linksTableId: 'GisVinculos',
saveButtonId: 'BtnGuardarGIS',
developmentId: 0,
stageId: 0,
saveUrl: '/Ashx/FraccionamientoGis.ashx',
getUrl: '/Ashx/FraccionamientoGis.ashx',
lots: [],
existingLinks: [],
initialZoom: 16,
center: { lat: 29.072967, lng: -110.955919 }
}, options || {});
}
function createState(options) {
var state = {
options: normalizeOptions(options),
map: null,
data: null,
geojson: null,
file: null,
polygonKeys: [],
polygonNameByKey: {},
linksByPolygonKey: {},
polygonByLotId: {},
lotsById: {}
};
$.each(state.options.lots || [], function (_, lot) {
var id = parseInt(lot.CveFraccionamientoLote || lot.cveFraccionamientoLote, 10);
if (id > 0) {
state.lotsById[id] = lot;
}
});
$.each(state.options.existingLinks || [], function (_, link) {
var key = link.GISPoligonoClave || link.gisPoligonoClave;
var lotId = parseInt(link.CveFraccionamientoLote || link.cveFraccionamientoLote, 10);
if (key && lotId > 0) {
state.linksByPolygonKey[key] = lotId;
state.polygonByLotId[lotId] = key;
}
});
return state;
}
function fnv1a(text) {
var hash = 0x811c9dc5;
var index;
for (index = 0; index < text.length; index++) {
hash ^= text.charCodeAt(index);
hash += (hash << 1) + (hash << 4) +
(hash << 7) + (hash << 8) + (hash << 24);
}
return ('00000000' + (hash >>> 0).toString(16)).slice(-8);
}
function polygonKey(feature) {
if (feature.id !== undefined && feature.id !== null && String(feature.id).trim() !== '') {
return 'id:' + String(feature.id).trim();
}
return 'geom:' + fnv1a(JSON.stringify(feature.geometry || {}));
}
function validateGeoJson(value) {
if (!value || value.type !== 'FeatureCollection' || !$.isArray(value.features)) {
throw new Error('El archivo .gis debe contener un GeoJSON FeatureCollection válido.');
}
if (value.features.length === 0) {
throw new Error('El archivo .gis no contiene polígonos.');
}
var keys = {};
var polygons = 0;
$.each(value.features, function (_, feature) {
if (!feature || !feature.geometry) {
return;
}
var geometryType = feature.geometry.type;
if (geometryType !== 'Polygon' && geometryType !== 'MultiPolygon') {
return;
}
var key = polygonKey(feature);
if (keys[key]) {
throw new Error('El archivo contiene dos polígonos con la misma clave GIS.');
}
keys[key] = true;
feature.properties = feature.properties || {};
feature.properties._sigPolygonKey = key;
polygons++;
});
if (polygons === 0) {
throw new Error('El archivo .gis no contiene geometrías Polygon o MultiPolygon.');
}
return value;
}
function ensureGoogleMaps() {
if (!window.google || !google.maps || !google.maps.Data) {
throw new Error('Google Maps no está disponible en la página.');
}
}
function initMap(state) {
ensureGoogleMaps();
var element = document.getElementById(state.options.mapId);
if (!element) {
throw new Error('No se encontró el contenedor del mapa GIS.');
}
state.map = new google.maps.Map(element, {
center: state.options.center,
zoom: state.options.initialZoom,
mapTypeId: 'roadmap'
});
state.data = new google.maps.Data({ map: state.map });
state.data.setStyle(function (feature) {
var key = feature.getProperty('_sigPolygonKey');
var linked = !!state.linksByPolygonKey[key];
return {
fillOpacity: linked ? 0.38 : 0.18,
strokeWeight: linked ? 3 : 2,
clickable: true
};
});
state.data.addListener('click', function (event) {
var key = event.feature.getProperty('_sigPolygonKey');
openAssignModal(state, key);
});
}
function clearMap(state) {
if (!state.data) {
return;
}
state.data.forEach(function (feature) {
state.data.remove(feature);
});
state.polygonKeys = [];
state.polygonNameByKey = {};
}
function loadGeoJson(state, value) {
value = validateGeoJson(value);
clearMap(state);
state.geojson = value;
$.each(value.features, function (_, feature) {
if (!feature || !feature.geometry ||
(feature.geometry.type !== 'Polygon' && feature.geometry.type !== 'MultiPolygon')) {
return;
}
var key = feature.properties._sigPolygonKey;
var name = feature.properties.Nombre ||
feature.properties.nombre ||
key;
state.polygonKeys.push(key);
state.polygonNameByKey[key] = name;
});
state.data.addGeoJson(value);
removeLinksNotInFile(state);
fitMap(state);
renderLinks(state);
state.data.setStyle(state.data.getStyle());
}
function removeLinksNotInFile(state) {
var valid = {};
$.each(state.polygonKeys, function (_, key) {
valid[key] = true;
});
$.each($.extend({}, state.linksByPolygonKey), function (key, lotId) {
if (!valid[key]) {
delete state.linksByPolygonKey[key];
delete state.polygonByLotId[lotId];
}
});
}
function fitMap(state) {
var bounds = new google.maps.LatLngBounds();
var hasPoint = false;
state.data.forEach(function (feature) {
feature.getGeometry().forEachLatLng(function (latLng) {
bounds.extend(latLng);
hasPoint = true;
});
});
if (hasPoint) {
state.map.fitBounds(bounds);
}
}
function lotName(state, lotId) {
var lot = state.lotsById[lotId];
if (!lot) {
return 'Lote ' + lotId;
}
return lot.Nombre || lot.nombre || ('Lote ' + lotId);
}
function openAssignModal(state, polygonKeyValue) {
var modalId = 'SigGisAssignModal';
var $modal = $('#' + modalId);
if ($modal.length === 0) {
$modal = $(
'<div class="modal fade" id="' + modalId + '" tabindex="-1" role="dialog">' +
'<div class="modal-dialog" role="document">' +
'<div class="modal-content">' +
'<div class="modal-header">' +
'<button type="button" class="close" data-dismiss="modal" aria-label="Cerrar"><span aria-hidden="true">&times;</span></button>' +
'<h4 class="modal-title">Vincular polígono con lote</h4>' +
'</div>' +
'<div class="modal-body">' +
'<div class="form-group">' +
'<label>Polígono</label>' +
'<p class="form-control-static" data-sig-gis-polygon-name></p>' +
'</div>' +
'<div class="form-group">' +
'<label for="SigGisAssignLot">Lote</label>' +
'<select id="SigGisAssignLot" class="form-control"></select>' +
'</div>' +
'</div>' +
'<div class="modal-footer">' +
'<button type="button" class="btn btn-default" data-dismiss="modal">Cancelar</button>' +
'<button type="button" class="btn btn-danger" data-sig-gis-unlink>Quitar vínculo</button>' +
'<button type="button" class="btn btn-primary" data-sig-gis-assign>Vincular</button>' +
'</div>' +
'</div>' +
'</div>' +
'</div>');
$('body').append($modal);
}
var currentLotId = state.linksByPolygonKey[polygonKeyValue] || 0;
var $select = $modal.find('#SigGisAssignLot').empty();
$select.append($('<option></option>').val('').text('Seleccione un lote'));
$.each(state.lotsById, function (lotIdText, lot) {
var lotId = parseInt(lotIdText, 10);
var assignedPolygon = state.polygonByLotId[lotId];
if (assignedPolygon && assignedPolygon !== polygonKeyValue) {
return;
}
$select.append(
$('<option></option>')
.val(lotId)
.text(lotName(state, lotId))
);
});
if (currentLotId) {
$select.val(String(currentLotId));
}
$modal.find('[data-sig-gis-polygon-name]').text(
state.polygonNameByKey[polygonKeyValue] || polygonKeyValue
);
$modal.off('.sigGisAssign');
$modal.on('click.sigGisAssign', '[data-sig-gis-assign]', function () {
var lotId = parseInt($select.val(), 10);
if (!lotId) {
showMessage('Seleccione un lote antes de vincular.', true);
return;
}
assign(state, polygonKeyValue, lotId);
$modal.modal('hide');
});
$modal.on('click.sigGisAssign', '[data-sig-gis-unlink]', function () {
unlink(state, polygonKeyValue);
$modal.modal('hide');
});
$modal.modal('show');
}
function assign(state, polygonKeyValue, lotId) {
var priorLot = state.linksByPolygonKey[polygonKeyValue];
if (priorLot) {
delete state.polygonByLotId[priorLot];
}
var priorPolygon = state.polygonByLotId[lotId];
if (priorPolygon && priorPolygon !== polygonKeyValue) {
delete state.linksByPolygonKey[priorPolygon];
}
state.linksByPolygonKey[polygonKeyValue] = lotId;
state.polygonByLotId[lotId] = polygonKeyValue;
state.data.revertStyle();
renderLinks(state);
}
function unlink(state, polygonKeyValue) {
var lotId = state.linksByPolygonKey[polygonKeyValue];
if (lotId) {
delete state.polygonByLotId[lotId];
}
delete state.linksByPolygonKey[polygonKeyValue];
state.data.revertStyle();
renderLinks(state);
}
function renderLinks(state) {
var $target = $('#' + state.options.linksTableId);
if ($target.length === 0) {
return;
}
var $table = $('<table class="table table-striped table-hover Widthall"></table>');
var $body = $('<tbody></tbody>');
$table.append(
'<thead><tr><th>Polígono GIS</th><th>Lote vinculado</th><th>Estado</th></tr></thead>'
);
$.each(state.polygonKeys, function (_, key) {
var lotId = state.linksByPolygonKey[key] || 0;
var $row = $('<tr></tr>');
$('<td></td>').text(state.polygonNameByKey[key] || key).appendTo($row);
$('<td></td>').text(lotId ? lotName(state, lotId) : 'Sin vincular').appendTo($row);
$('<td></td>').text(lotId ? 'Vinculado' : 'Pendiente').appendTo($row);
$body.append($row);
});
$table.append($body);
$target.empty().append($table);
}
function collectLinks(state) {
var links = [];
$.each(state.polygonKeys, function (_, key) {
var lotId = state.linksByPolygonKey[key];
if (lotId) {
links.push({
CveFraccionamientoLote: lotId,
GISPoligonoClave: key
});
}
});
return links;
}
function validateBeforeSave(state) {
if (!state.geojson) {
return 'Seleccione un archivo .gis válido.';
}
var links = collectLinks(state);
if (links.length !== state.polygonKeys.length) {
return 'Debe vincular cada polígono del archivo GIS con un lote antes de guardar.';
}
if (state.polygonKeys.length === 0) {
return 'El archivo GIS no contiene polígonos para guardar.';
}
return null;
}
function save(state) {
var validation = validateBeforeSave(state);
if (validation) {
showMessage(validation, true);
return;
}
var form = new FormData();
form.append('metodo', 'Save');
form.append('cveFraccionamiento', state.options.developmentId);
form.append('cveFraccionamientoEtapa', state.options.stageId);
form.append('vinculos', JSON.stringify(collectLinks(state)));
if (state.file) {
form.append('archivo', state.file, state.file.name);
}
var $button = $('#' + state.options.saveButtonId);
$button.prop('disabled', true);
$.ajax({
url: state.options.saveUrl,
type: 'POST',
data: form,
processData: false,
contentType: false,
dataType: 'json'
}).done(function (response) {
if (!response || response.success !== true) {
showServerError(response);
return;
}
showMessage(response.message || 'El archivo GIS y sus vínculos se guardaron correctamente.', false);
$(document).trigger('siggis:saved', [response]);
}).fail(function (xhr) {
var response = null;
try {
response = xhr.responseJSON || JSON.parse(xhr.responseText);
} catch (_) {
response = null;
}
showServerError(response);
}).always(function () {
$button.prop('disabled', false);
});
}
function showServerError(response) {
var message = response && response.message
? response.message
: 'No fue posible guardar la información GIS.';
var code = response && response.codigo
? response.codigo
: 'SIG-GIS';
if (window.SIGErrorUI && typeof window.SIGErrorUI.mostrar === 'function') {
window.SIGErrorUI.mostrar(code, message);
} else {
showMessage(message + (code ? ' Código: ' + code : ''), true);
}
}
function showMessage(message, isError) {
if (window.bootbox && typeof window.bootbox.alert === 'function') {
window.bootbox.alert(message);
return;
}
if (window.console && isError) {
console.warn(message);
}
window.alert(message);
}
function bindFile(state) {
var $input = $('#' + state.options.fileInputId);
if ($input.length === 0) {
throw new Error('No se encontró el control local para el archivo GIS.');
}
$input.off('.sigGis');
$input.on('adsdropzone:fileadded.sigGis', function (_, file, text) {
try {
state.file = file;
loadGeoJson(state, JSON.parse(text));
$(document).trigger('siggis:fileloaded', [state.geojson]);
} catch (ex) {
state.file = null;
state.geojson = null;
clearMap(state);
renderLinks(state);
if (window.ADSLocalDropzone) {
window.ADSLocalDropzone.clear(state.options.fileInputId);
}
showMessage(ex.message || 'El archivo GIS no es válido.', true);
}
});
$input.on('adsdropzone:fileremoved.sigGis', function () {
state.file = null;
state.geojson = null;
clearMap(state);
renderLinks(state);
});
}
function bindSave(state) {
$('#' + state.options.saveButtonId)
.off('click.sigGis')
.on('click.sigGis', function (event) {
event.preventDefault();
save(state);
});
}
api.init = function (options) {
var normalized = normalizeOptions(options);
var mapElement = document.getElementById(normalized.mapId);
if (!mapElement) {
return null;
}
if (mapElement.getAttribute('data-sig-fraccionamiento-gis-initialized') === '1') {
return current;
}
current = createState(normalized);
try {
initMap(current);
bindFile(current);
bindSave(current);
renderLinks(current);
mapElement.setAttribute('data-sig-fraccionamiento-gis-initialized', '1');
return current;
} catch (ex) {
console.error(ex);
mapElement.removeAttribute('data-sig-fraccionamiento-gis-initialized');
showMessage(ex.message || 'No fue posible inicializar el visor GIS.', true);
return null;
}
};
api.getState = function () {
return current;
};
api.getLinks = function () {
return current ? collectLinks(current) : [];
};
api.getLotForPolygon = function (polygonKeyValue) {
if (!current) {
return null;
}
var lotId = current.linksByPolygonKey[polygonKeyValue];
return lotId ? current.lotsById[lotId] || null : null;
};
function autoInit() {
if (window.FraccionamientoGISOptions) {
api.init(window.FraccionamientoGISOptions);
}
}
if (window.Sys && Sys.Application) {
if (window.SIG_FRACCIONAMIENTO_GIS_LOAD_REGISTERED !== true) {
window.SIG_FRACCIONAMIENTO_GIS_LOAD_REGISTERED = true;
Sys.Application.add_load(autoInit);
}
} else {
$(autoInit);
}
})(window, window.jQuery);

View File

@@ -0,0 +1,14 @@
.sig-gis-map {
width: 100%;
min-height: 520px;
}
.ads-local-dropzone {
min-height: 125px;
}
.sig-gis-links {
margin-top: 15px;
max-height: 320px;
overflow-y: auto;
}