diff --git a/AGENTS.md b/AGENTS.md index a51874d..5b5a32d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -494,6 +494,7 @@ Rules: - Mandatory: read and follow `AGENTS_WEBFORMS_PARTIAL_POSTBACKS.md` for the single master form, ScriptManager ownership, UpdatePanel usage, and partial-postback rules. - Mandatory: read and follow `AGENTS_JAVASCRIPT_INITIALIZATION.md` for per-element initialization flags, global event guards, and idempotent control setup after partial postbacks. - Mandatory: read and follow `AGENTS_CATALOG_STRUCTURE.md` for catalog titles, breadcrumbs, actions, filters, indicators, grids, quick-create modals, and detail pages. +- Mandatory: read and follow `AGENTS_DOCUMENT_STORAGE.md` for document folders, client access validation, protected file retrieval, and GIS file naming. - Keep stored procedure names centralized. - Repositories must use `CommandType.StoredProcedure` and parameterized inputs/outputs. - Never concatenate raw user input into SQL. diff --git a/AGENTS_DOCUMENT_STORAGE.md b/AGENTS_DOCUMENT_STORAGE.md new file mode 100644 index 0000000..d9095d3 --- /dev/null +++ b/AGENTS_DOCUMENT_STORAGE.md @@ -0,0 +1,44 @@ +# Reglas obligatorias para almacenamiento de documentos + +Estas reglas complementan `AGENTS.md` y aplican a todo archivo persistido por la aplicación. + +## Estructura física + +La raíz de documentos de la aplicación es: + +```text +Documentos/ + Generales/ + Clientes/ + {CveCliente}/ +``` + +- `Generales` contiene documentos que no pertenecen a un cliente específico. +- `Clientes` contiene una carpeta por `CveCliente`. +- Antes de guardar un archivo, el servicio debe asegurar la existencia de `Documentos`, `Documentos/Generales`, `Documentos/Clientes` y de la carpeta específica del cliente cuando aplique. +- No asumir que las carpetas ya existen. Usar una operación idempotente como `Directory.CreateDirectory` para crearlas cuando falten. +- La misma regla aplica a futuras subcarpetas de documentos. + +## Acceso y seguridad + +- Las páginas WebForms y handlers no deben construir rutas físicas ni abrir archivos de cliente por su cuenta. +- Todo guardado y obtención de archivos debe pasar por un servicio dedicado de documentos. Un handler de descarga sólo puede transmitir el archivo previamente autorizado y resuelto por ese servicio. +- Antes de construir o abrir una ruta física, el servicio debe resolver en servidor el cliente propietario a partir de la entidad de negocio y validar el acceso del usuario. +- No confiar para autorización en `CveCliente`, rutas físicas, rutas relativas ni nombres de archivo recibidos desde el navegador. +- El rol `Empresa` se considera desarrollador y sólo puede acceder a documentos de clientes activos que tenga asignados en `Sis_UsuariosClientes`. +- Los demás roles activos, incluidos `Administrador` y `SuperUsuario`, tienen acceso global a documentos, salvo que una regla funcional más restrictiva se defina explícitamente para un módulo. +- Las rutas físicas bajo `Documentos` no deben exponerse como URLs públicas. Las descargas deben pasar por un endpoint autenticado que vuelva a validar el acceso mediante el servicio. + +## GIS de etapas de fraccionamiento + +- El botón `Agregar GIS` carga un archivo físico con extensión `.gis`; no solicita nombre ni URL de visualización al usuario. +- El archivo se guarda en `Documentos/Clientes/{CveCliente}/`. +- El nombre se genera exclusivamente en servidor con el formato: + +```text +gis-{CveFraccionamiento}-{CveFraccionamientoEtapa}.gis +``` + +- El usuario no puede elegir ni modificar el nombre final del archivo. +- Al reemplazar el GIS de una etapa se conserva el mismo nombre físico y se sobrescribe el archivo correspondiente. +- La obtención del GIS debe usar el servicio de documentos y nunca una URL directa al folder `Documentos`. diff --git a/SIG.Web/App_Code/Configuration/AppServices.cs b/SIG.Web/App_Code/Configuration/AppServices.cs index cbdec1f..677725e 100644 --- a/SIG.Web/App_Code/Configuration/AppServices.cs +++ b/SIG.Web/App_Code/Configuration/AppServices.cs @@ -2,6 +2,7 @@ using System; using System.Configuration; using SIG.Web.AppCode.Contracts.Bitacora; using SIG.Web.AppCode.Contracts.Database; +using SIG.Web.AppCode.Contracts.Documentos; using SIG.Web.AppCode.Contracts.Facturacion; using SIG.Web.AppCode.Contracts.Fraccionamientos; using SIG.Web.AppCode.Contracts.General; @@ -13,6 +14,7 @@ using SIG.Web.AppCode.Notifications.Contracts; using SIG.Web.AppCode.Notifications.Services; using SIG.Web.AppCode.Repositories.Bitacora; using SIG.Web.AppCode.Repositories.Database; +using SIG.Web.AppCode.Repositories.Documentos; using SIG.Web.AppCode.Repositories.Facturacion; using SIG.Web.AppCode.Repositories.General; using SIG.Web.AppCode.Repositories.Menu; @@ -20,6 +22,7 @@ using SIG.Web.AppCode.Repositories.Fraccionamientos; using SIG.Web.AppCode.Repositories.Security; using SIG.Web.AppCode.Repositories.Tramites; using SIG.Web.AppCode.Services.Bitacora; +using SIG.Web.AppCode.Services.Documentos; using SIG.Web.AppCode.Services.Facturacion; using SIG.Web.AppCode.Services.Fraccionamientos; using SIG.Web.AppCode.Services.Menu; @@ -41,6 +44,7 @@ namespace SIG.Web.AppCode.Configuration private static IUserAdministrationRepository UserAdministrationRepository { get; set; } public static IUserAdministrationService UserAdministrationService { get; private set; } public static IGeneralCatalogRepository GeneralCatalogRepository { get; private set; } + public static IClienteDocumentoService ClienteDocumentoService { get; private set; } public static IFraccionamientoService FraccionamientoService { get; private set; } public static ITramiteCatalogService TramiteCatalogService { get; private set; } public static ICfdiTimbradoService CfdiTimbradoService { get; private set; } @@ -83,6 +87,8 @@ namespace SIG.Web.AppCode.Configuration PasswordHasher, BitacoraService); GeneralCatalogRepository = new GeneralCatalogRepository(DbExecutor); + ClienteDocumentoService = new ClienteDocumentoService( + new ClienteDocumentoRepository(DbExecutor)); FraccionamientoService = new FraccionamientoService( new FraccionamientoRepository(DbExecutor), BitacoraService); diff --git a/SIG.Web/App_Code/Contracts/Documentos/IClienteDocumentoRepository.cs b/SIG.Web/App_Code/Contracts/Documentos/IClienteDocumentoRepository.cs new file mode 100644 index 0000000..1269f5b --- /dev/null +++ b/SIG.Web/App_Code/Contracts/Documentos/IClienteDocumentoRepository.cs @@ -0,0 +1,13 @@ +using SIG.Web.AppCode.Models.Common; +using SIG.Web.AppCode.Models.Documentos; + +namespace SIG.Web.AppCode.Contracts.Documentos +{ + public interface IClienteDocumentoRepository + { + DbResult GetStageContext( + int stageId, + int executingUser, + string domainName); + } +} diff --git a/SIG.Web/App_Code/Contracts/Documentos/IClienteDocumentoService.cs b/SIG.Web/App_Code/Contracts/Documentos/IClienteDocumentoService.cs new file mode 100644 index 0000000..cdf99ee --- /dev/null +++ b/SIG.Web/App_Code/Contracts/Documentos/IClienteDocumentoService.cs @@ -0,0 +1,28 @@ +using System.IO; +using SIG.Web.AppCode.Models.Common; +using SIG.Web.AppCode.Models.Documentos; + +namespace SIG.Web.AppCode.Contracts.Documentos +{ + public interface IClienteDocumentoService + { + DbResult GetStageContext( + int stageId, + int executingUser, + string domainName); + + DbResult SaveStageGis( + int developmentId, + int stageId, + string originalFileName, + Stream content, + long contentLength, + int executingUser, + string domainName); + + DbResult GetStageGis( + int stageId, + int executingUser, + string domainName); + } +} diff --git a/SIG.Web/App_Code/Contracts/Fraccionamientos/IFraccionamientoRepository.cs b/SIG.Web/App_Code/Contracts/Fraccionamientos/IFraccionamientoRepository.cs index 53b280e..23dc8d4 100644 --- a/SIG.Web/App_Code/Contracts/Fraccionamientos/IFraccionamientoRepository.cs +++ b/SIG.Web/App_Code/Contracts/Fraccionamientos/IFraccionamientoRepository.cs @@ -24,7 +24,7 @@ namespace SIG.Web.AppCode.Contracts.Fraccionamientos int executingUser, string domainName); DbResult SaveStageGis( - FraccionamientoEtapaGisModel gis, + int stageId, int executingUser, string domainName); DbResult> ListStageProcedures(int developmentId, int executingUser, string domainName); diff --git a/SIG.Web/App_Code/Contracts/Fraccionamientos/IFraccionamientoService.cs b/SIG.Web/App_Code/Contracts/Fraccionamientos/IFraccionamientoService.cs index 37b5ed2..73b5c05 100644 --- a/SIG.Web/App_Code/Contracts/Fraccionamientos/IFraccionamientoService.cs +++ b/SIG.Web/App_Code/Contracts/Fraccionamientos/IFraccionamientoService.cs @@ -27,7 +27,7 @@ namespace SIG.Web.AppCode.Contracts.Fraccionamientos int executingUser, string domainName); DbResult SaveStageGis( - FraccionamientoEtapaGisModel gis, + int stageId, int executingUser, string domainName); DbResult> ListStageProcedures(int developmentId, int executingUser, string domainName); diff --git a/SIG.Web/App_Code/Handlers/Fraccionamientos/FraccionamientoGisHandler.cs b/SIG.Web/App_Code/Handlers/Fraccionamientos/FraccionamientoGisHandler.cs new file mode 100644 index 0000000..dc4e703 --- /dev/null +++ b/SIG.Web/App_Code/Handlers/Fraccionamientos/FraccionamientoGisHandler.cs @@ -0,0 +1,78 @@ +using System; +using System.IO; +using System.Web; +using System.Web.SessionState; +using SIG.Web.AppCode.Configuration; +using SIG.Web.AppCode.Models.Common; +using SIG.Web.AppCode.Models.Documentos; +using SIG.Web.AppCode.Session; + +namespace SIG.Web.Pages.Fraccionamientos +{ + public sealed class FraccionamientoGisHandler : IHttpHandler, IRequiresSessionState + { + public bool IsReusable + { + get + { + return false; + } + } + + public void ProcessRequest(HttpContext context) + { + if (!AppServices.UserContext.IsAuthenticated()) + { + context.Response.StatusCode = 401; + return; + } + + int stageId; + + if (!int.TryParse( + context.Request.QueryString["etapa"], + out stageId) || + stageId <= 0) + { + context.Response.StatusCode = 400; + return; + } + + UserSessionModel currentUser = + AppServices.UserContext.GetCurrentUser(); + + if (currentUser == null) + { + context.Response.StatusCode = 401; + return; + } + + DbResult result = + AppServices.ClienteDocumentoService.GetStageGis( + stageId, + currentUser.CveUsuario, + currentUser.NombreDominio); + + if (!result.Success || + result.Data == null || + string.IsNullOrWhiteSpace(result.Data.RutaFisica) || + !File.Exists(result.Data.RutaFisica)) + { + context.Response.StatusCode = 404; + return; + } + + ClienteDocumentoArchivoModel file = result.Data; + + context.Response.Clear(); + context.Response.Cache.SetCacheability(HttpCacheability.NoCache); + context.Response.Cache.SetNoStore(); + context.Response.ContentType = file.ContentType; + context.Response.AddHeader( + "Content-Disposition", + "attachment; filename=\"" + file.NombreArchivo + "\""); + context.Response.TransmitFile(file.RutaFisica); + context.ApplicationInstance.CompleteRequest(); + } + } +} diff --git a/SIG.Web/App_Code/Models/Documentos/ClienteDocumentoModels.cs b/SIG.Web/App_Code/Models/Documentos/ClienteDocumentoModels.cs new file mode 100644 index 0000000..556d3d4 --- /dev/null +++ b/SIG.Web/App_Code/Models/Documentos/ClienteDocumentoModels.cs @@ -0,0 +1,20 @@ +namespace SIG.Web.AppCode.Models.Documentos +{ + public sealed class ClienteDocumentoEtapaContextoModel + { + public int CveFraccionamientoEtapa { get; set; } + public int CveFraccionamiento { get; set; } + public int CveCliente { get; set; } + public string Etapa { get; set; } + } + + public sealed class ClienteDocumentoArchivoModel + { + public int CveCliente { get; set; } + public int CveFraccionamiento { get; set; } + public int CveFraccionamientoEtapa { get; set; } + public string NombreArchivo { get; set; } + public string RutaFisica { get; set; } + public string ContentType { get; set; } + } +} diff --git a/SIG.Web/App_Code/Repositories/Documentos/ClienteDocumentoRepository.cs b/SIG.Web/App_Code/Repositories/Documentos/ClienteDocumentoRepository.cs new file mode 100644 index 0000000..e85969f --- /dev/null +++ b/SIG.Web/App_Code/Repositories/Documentos/ClienteDocumentoRepository.cs @@ -0,0 +1,52 @@ +using System.Data; +using SIG.Web.AppCode.Contracts.Database; +using SIG.Web.AppCode.Contracts.Documentos; +using SIG.Web.AppCode.Models.Common; +using SIG.Web.AppCode.Models.Documentos; +using SIG.Web.AppCode.Repositories.Database; +using SIG.Web.AppCode.Utilities; + +namespace SIG.Web.AppCode.Repositories.Documentos +{ + public sealed class ClienteDocumentoRepository : IClienteDocumentoRepository + { + private readonly IDbExecutor _db; + + public ClienteDocumentoRepository(IDbExecutor db) + { + _db = db; + } + + public DbResult GetStageContext( + int stageId, + int executingUser, + string domainName) + { + return _db.Execute(domainName, connection => + { + using (IDbCommand command = connection.CreateCommand()) + { + StoredProcedureCommand.Configure( + command, + "dbo.PA_Fraccionamientos_EtapaDocumentoGet", + executingUser); + StoredProcedureCommand.AddInput( + command, + "@CveFraccionamientoEtapa", + stageId, + DbType.Int32); + + ClienteDocumentoEtapaContextoModel value; + + using (IDataReader reader = command.ExecuteReader()) + { + value = reader.ToObject(); + } + + StoredProcedureCommand.EnsureSuccess(command); + return value; + } + }); + } + } +} diff --git a/SIG.Web/App_Code/Repositories/Fraccionamientos/FraccionamientoRepository.cs b/SIG.Web/App_Code/Repositories/Fraccionamientos/FraccionamientoRepository.cs index 1be752c..569cfb8 100644 --- a/SIG.Web/App_Code/Repositories/Fraccionamientos/FraccionamientoRepository.cs +++ b/SIG.Web/App_Code/Repositories/Fraccionamientos/FraccionamientoRepository.cs @@ -156,7 +156,7 @@ namespace SIG.Web.AppCode.Repositories.Fraccionamientos } public DbResult SaveStageGis( - FraccionamientoEtapaGisModel gis, + int stageId, int executingUser, string domainName) { @@ -164,24 +164,11 @@ namespace SIG.Web.AppCode.Repositories.Fraccionamientos "dbo.PA_Fraccionamientos_EtapaGISSave", executingUser, domainName, - command => - { - StoredProcedureCommand.AddInput( - command, - "@CveFraccionamientoEtapa", - gis.CveFraccionamientoEtapa, - DbType.Int32); - StoredProcedureCommand.AddInput( - command, - "@Nombre", - gis.Nombre, - DbType.String); - StoredProcedureCommand.AddInput( - command, - "@UrlVisualizacion", - gis.UrlVisualizacion, - DbType.String); - }); + command => StoredProcedureCommand.AddInput( + command, + "@CveFraccionamientoEtapa", + stageId, + DbType.Int32)); } public DbResult> ListStageProcedures( diff --git a/SIG.Web/App_Code/Services/Documentos/ClienteDocumentoService.cs b/SIG.Web/App_Code/Services/Documentos/ClienteDocumentoService.cs new file mode 100644 index 0000000..3cc1164 --- /dev/null +++ b/SIG.Web/App_Code/Services/Documentos/ClienteDocumentoService.cs @@ -0,0 +1,283 @@ +using System; +using System.Globalization; +using System.IO; +using System.Web; +using SIG.Web.AppCode.Contracts.Documentos; +using SIG.Web.AppCode.Models.Common; +using SIG.Web.AppCode.Models.Documentos; +using SIG.Web.AppCode.Services.Logging; + +namespace SIG.Web.AppCode.Services.Documentos +{ + public sealed class ClienteDocumentoService : IClienteDocumentoService + { + private const string GisExtension = ".gis"; + private const string GisContentType = "application/octet-stream"; + + private readonly IClienteDocumentoRepository _repository; + + public ClienteDocumentoService(IClienteDocumentoRepository repository) + { + _repository = repository; + } + + public DbResult GetStageContext( + int stageId, + int executingUser, + string domainName) + { + if (stageId <= 0) + { + return Fail( + "Seleccione una etapa válida."); + } + + return _repository.GetStageContext( + stageId, + executingUser, + domainName); + } + + public DbResult SaveStageGis( + int developmentId, + int stageId, + string originalFileName, + Stream content, + long contentLength, + int executingUser, + string domainName) + { + if (developmentId <= 0 || stageId <= 0) + { + return Fail( + "Seleccione un fraccionamiento y una etapa válidos."); + } + + if (content == null || contentLength <= 0) + { + return Fail( + "Seleccione un archivo GIS válido."); + } + + if (!string.Equals( + Path.GetExtension(originalFileName), + GisExtension, + StringComparison.OrdinalIgnoreCase)) + { + return Fail( + "El archivo seleccionado debe tener extensión .gis."); + } + + DbResult contextResult = + _repository.GetStageContext( + stageId, + executingUser, + domainName); + + if (!contextResult.Success) + { + return DbResult.Fail( + contextResult.UserMessage, + contextResult.TechnicalMessage, + contextResult.Exception); + } + + ClienteDocumentoEtapaContextoModel stageContext = + contextResult.Data; + + if (stageContext == null || + stageContext.CveCliente <= 0 || + stageContext.CveFraccionamiento != developmentId || + stageContext.CveFraccionamientoEtapa != stageId) + { + return Fail( + "La etapa no pertenece al fraccionamiento actual o no está disponible para el usuario."); + } + + try + { + string clientDirectory = EnsureClientDirectory( + stageContext.CveCliente); + string fileName = BuildStageGisFileName(stageContext); + string physicalPath = Path.Combine( + clientDirectory, + fileName); + + using (var fileStream = new FileStream( + physicalPath, + FileMode.Create, + FileAccess.Write, + FileShare.None)) + { + content.CopyTo(fileStream); + } + + return DbResult.Ok( + BuildFileModel( + stageContext, + fileName, + physicalPath)); + } + catch (Exception ex) + { + const string userMessage = + "No fue posible guardar el archivo GIS; favor de contactar a Sistemas."; + ExceptionLogger.Log( + ex, + "ClienteDocumentoService.SaveStageGis", + userMessage); + return DbResult.Fail( + userMessage, + ex.Message, + ex); + } + } + + public DbResult GetStageGis( + int stageId, + int executingUser, + string domainName) + { + if (stageId <= 0) + { + return Fail( + "Seleccione una etapa válida."); + } + + DbResult contextResult = + _repository.GetStageContext( + stageId, + executingUser, + domainName); + + if (!contextResult.Success) + { + return DbResult.Fail( + contextResult.UserMessage, + contextResult.TechnicalMessage, + contextResult.Exception); + } + + ClienteDocumentoEtapaContextoModel stageContext = + contextResult.Data; + + if (stageContext == null || + stageContext.CveCliente <= 0 || + stageContext.CveFraccionamiento <= 0 || + stageContext.CveFraccionamientoEtapa != stageId) + { + return Fail( + "La etapa no está disponible para el usuario actual."); + } + + try + { + string clientDirectory = BuildClientDirectory( + stageContext.CveCliente); + string fileName = BuildStageGisFileName(stageContext); + string physicalPath = Path.Combine( + clientDirectory, + fileName); + + if (!File.Exists(physicalPath)) + { + return Fail( + "No se encontró el archivo GIS de la etapa."); + } + + return DbResult.Ok( + BuildFileModel( + stageContext, + fileName, + physicalPath)); + } + catch (Exception ex) + { + const string userMessage = + "No fue posible obtener el archivo GIS; favor de contactar a Sistemas."; + ExceptionLogger.Log( + ex, + "ClienteDocumentoService.GetStageGis", + userMessage); + return DbResult.Fail( + userMessage, + ex.Message, + ex); + } + } + + private static ClienteDocumentoArchivoModel BuildFileModel( + ClienteDocumentoEtapaContextoModel stageContext, + string fileName, + string physicalPath) + { + return new ClienteDocumentoArchivoModel + { + CveCliente = stageContext.CveCliente, + CveFraccionamiento = stageContext.CveFraccionamiento, + CveFraccionamientoEtapa = + stageContext.CveFraccionamientoEtapa, + NombreArchivo = fileName, + RutaFisica = physicalPath, + ContentType = GisContentType + }; + } + + private static string BuildStageGisFileName( + ClienteDocumentoEtapaContextoModel stageContext) + { + return string.Format( + CultureInfo.InvariantCulture, + "gis-{0}-{1}.gis", + stageContext.CveFraccionamiento, + stageContext.CveFraccionamientoEtapa); + } + + private static string EnsureClientDirectory(int clientId) + { + string applicationRoot = GetApplicationRoot(); + string documentsDirectory = Path.Combine( + applicationRoot, + "Documentos"); + string generalDirectory = Path.Combine( + documentsDirectory, + "Generales"); + string clientsDirectory = Path.Combine( + documentsDirectory, + "Clientes"); + string clientDirectory = Path.Combine( + clientsDirectory, + clientId.ToString(CultureInfo.InvariantCulture)); + + Directory.CreateDirectory(documentsDirectory); + Directory.CreateDirectory(generalDirectory); + Directory.CreateDirectory(clientsDirectory); + Directory.CreateDirectory(clientDirectory); + + return clientDirectory; + } + + private static string BuildClientDirectory(int clientId) + { + return Path.Combine( + GetApplicationRoot(), + "Documentos", + "Clientes", + clientId.ToString(CultureInfo.InvariantCulture)); + } + + private static string GetApplicationRoot() + { + string applicationRoot = HttpRuntime.AppDomainAppPath; + + return string.IsNullOrWhiteSpace(applicationRoot) + ? AppDomain.CurrentDomain.BaseDirectory + : applicationRoot; + } + + private static DbResult Fail(string message) + { + return DbResult.Fail(message, message, null); + } + } +} diff --git a/SIG.Web/App_Code/Services/Fraccionamientos/FraccionamientoBitacoraFactory.cs b/SIG.Web/App_Code/Services/Fraccionamientos/FraccionamientoBitacoraFactory.cs index e58d8a7..d3ff85f 100644 --- a/SIG.Web/App_Code/Services/Fraccionamientos/FraccionamientoBitacoraFactory.cs +++ b/SIG.Web/App_Code/Services/Fraccionamientos/FraccionamientoBitacoraFactory.cs @@ -411,9 +411,16 @@ namespace SIG.Web.AppCode.Services.Fraccionamientos Uri uri; - return Uri.TryCreate(value, UriKind.Absolute, out uri) - ? uri.GetLeftPart(UriPartial.Path) - : "[URL no válida]"; + if (Uri.TryCreate(value, UriKind.Absolute, out uri)) + { + return uri.GetLeftPart(UriPartial.Path); + } + + int queryIndex = value.IndexOf('?'); + + return queryIndex >= 0 + ? value.Substring(0, queryIndex) + : value; } private static IList LotFields( diff --git a/SIG.Web/App_Code/Services/Fraccionamientos/FraccionamientoService.cs b/SIG.Web/App_Code/Services/Fraccionamientos/FraccionamientoService.cs index e74ebca..f2c0358 100644 --- a/SIG.Web/App_Code/Services/Fraccionamientos/FraccionamientoService.cs +++ b/SIG.Web/App_Code/Services/Fraccionamientos/FraccionamientoService.cs @@ -224,42 +224,19 @@ namespace SIG.Web.AppCode.Services.Fraccionamientos } public DbResult SaveStageGis( - FraccionamientoEtapaGisModel gis, + int stageId, int executingUser, string domainName) { - if (gis == null || gis.CveFraccionamientoEtapa <= 0) + if (stageId <= 0) { return Fail( "Seleccione una etapa válida."); } - gis.Nombre = TrimOrNull(gis.Nombre); - gis.UrlVisualizacion = TrimOrNull(gis.UrlVisualizacion); - - if (string.IsNullOrWhiteSpace(gis.Nombre)) - { - return Fail( - "Capture el nombre del GIS."); - } - - if (gis.Nombre.Length > 200) - { - return Fail( - "El nombre del GIS no puede exceder 200 caracteres."); - } - - if (string.IsNullOrWhiteSpace(gis.UrlVisualizacion) || - gis.UrlVisualizacion.Length > 2000 || - !IsValidHttpUrl(gis.UrlVisualizacion)) - { - return Fail( - "Capture una URL absoluta http o https válida para el GIS."); - } - return ExecuteInTransaction( () => _repository.SaveStageGis( - gis, + stageId, executingUser, domainName), result => result.HuboCambio @@ -884,17 +861,6 @@ namespace SIG.Web.AppCode.Services.Fraccionamientos return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } - private static bool IsValidHttpUrl(string value) - { - Uri uri; - - return Uri.TryCreate(value, UriKind.Absolute, out uri) && - string.IsNullOrEmpty(uri.UserInfo) && - !string.IsNullOrWhiteSpace(uri.Host) && - (uri.Scheme == Uri.UriSchemeHttp || - uri.Scheme == Uri.UriSchemeHttps); - } - private static DbResult Fail(string message) { return DbResult.Fail(message, message, new ArgumentException(message)); diff --git a/SIG.Web/Documentos/Clientes/4/gis-5-7.gis b/SIG.Web/Documentos/Clientes/4/gis-5-7.gis new file mode 100644 index 0000000..53b745c --- /dev/null +++ b/SIG.Web/Documentos/Clientes/4/gis-5-7.gis @@ -0,0 +1,412 @@ +{ + "type": "FeatureCollection", + "name": "Etapa1_10_lotes", + "crs": { + "type": "name", + "properties": { + "name": "EPSG:4326" + } + }, + "features": [ + { + "type": "Feature", + "properties": { + "Etapa": "Etapa 1", + "Nombre": "Umbrella-1", + "Tipo": "Lote", + "Calle": "Calle Demo 1", + "Catastro": "CAT-001", + "MetrosCuadrados": 200.0, + "Duplicador": 1, + "Estatus": "Activa" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -110.9555, + 29.09 + ], + [ + -110.95532, + 29.09 + ], + [ + -110.95532, + 29.090139999999998 + ], + [ + -110.9555, + 29.090139999999998 + ], + [ + -110.9555, + 29.09 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Etapa": "Etapa 1", + "Nombre": "Umbrella-2", + "Tipo": "Lote", + "Calle": "Calle Demo 1", + "Catastro": "CAT-002", + "MetrosCuadrados": 200.0, + "Duplicador": 1, + "Estatus": "Activa" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -110.9553, + 29.09 + ], + [ + -110.95512, + 29.09 + ], + [ + -110.95512, + 29.090139999999998 + ], + [ + -110.9553, + 29.090139999999998 + ], + [ + -110.9553, + 29.09 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Etapa": "Etapa 1", + "Nombre": "Umbrella-3", + "Tipo": "Lote", + "Calle": "Calle Demo 1", + "Catastro": "CAT-003", + "MetrosCuadrados": 200.0, + "Duplicador": 1, + "Estatus": "Activa" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -110.9551, + 29.09 + ], + [ + -110.95492, + 29.09 + ], + [ + -110.95492, + 29.090139999999998 + ], + [ + -110.9551, + 29.090139999999998 + ], + [ + -110.9551, + 29.09 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Etapa": "Etapa 1", + "Nombre": "Umbrella-4", + "Tipo": "Lote", + "Calle": "Calle Demo 1", + "Catastro": "CAT-004", + "MetrosCuadrados": 200.0, + "Duplicador": 1, + "Estatus": "Activa" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -110.9549, + 29.09 + ], + [ + -110.95472, + 29.09 + ], + [ + -110.95472, + 29.090139999999998 + ], + [ + -110.9549, + 29.090139999999998 + ], + [ + -110.9549, + 29.09 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Etapa": "Etapa 1", + "Nombre": "Umbrella-5", + "Tipo": "Lote", + "Calle": "Calle Demo 1", + "Catastro": "CAT-005", + "MetrosCuadrados": 200.0, + "Duplicador": 1, + "Estatus": "Activa" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -110.9547, + 29.09 + ], + [ + -110.95452, + 29.09 + ], + [ + -110.95452, + 29.090139999999998 + ], + [ + -110.9547, + 29.090139999999998 + ], + [ + -110.9547, + 29.09 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Etapa": "Etapa 1", + "Nombre": "Umbrella-6", + "Tipo": "Lote", + "Calle": "Calle Demo 2", + "Catastro": "CAT-006", + "MetrosCuadrados": 200.0, + "Duplicador": 1, + "Estatus": "Activa" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -110.9555, + 29.09016 + ], + [ + -110.95532, + 29.09016 + ], + [ + -110.95532, + 29.0903 + ], + [ + -110.9555, + 29.0903 + ], + [ + -110.9555, + 29.09016 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Etapa": "Etapa 1", + "Nombre": "Umbrella-7", + "Tipo": "Lote", + "Calle": "Calle Demo 2", + "Catastro": "CAT-007", + "MetrosCuadrados": 200.0, + "Duplicador": 1, + "Estatus": "Activa" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -110.9553, + 29.09016 + ], + [ + -110.95512, + 29.09016 + ], + [ + -110.95512, + 29.0903 + ], + [ + -110.9553, + 29.0903 + ], + [ + -110.9553, + 29.09016 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Etapa": "Etapa 1", + "Nombre": "Umbrella-8", + "Tipo": "Lote", + "Calle": "Calle Demo 2", + "Catastro": "CAT-008", + "MetrosCuadrados": 200.0, + "Duplicador": 1, + "Estatus": "Activa" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -110.9551, + 29.09016 + ], + [ + -110.95492, + 29.09016 + ], + [ + -110.95492, + 29.0903 + ], + [ + -110.9551, + 29.0903 + ], + [ + -110.9551, + 29.09016 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Etapa": "Etapa 1", + "Nombre": "Umbrella-9", + "Tipo": "Lote", + "Calle": "Calle Demo 2", + "Catastro": "CAT-009", + "MetrosCuadrados": 200.0, + "Duplicador": 1, + "Estatus": "Activa" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -110.9549, + 29.09016 + ], + [ + -110.95472, + 29.09016 + ], + [ + -110.95472, + 29.0903 + ], + [ + -110.9549, + 29.0903 + ], + [ + -110.9549, + 29.09016 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "Etapa": "Etapa 1", + "Nombre": "Umbrella-10", + "Tipo": "Lote", + "Calle": "Calle Demo 2", + "Catastro": "CAT-010", + "MetrosCuadrados": 200.0, + "Duplicador": 1, + "Estatus": "Activa" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -110.9547, + 29.09016 + ], + [ + -110.95452, + 29.09016 + ], + [ + -110.95452, + 29.0903 + ], + [ + -110.9547, + 29.0903 + ], + [ + -110.9547, + 29.09016 + ] + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/SIG.Web/Documentos/Nuevo Documento de texto.txt b/SIG.Web/Documentos/Nuevo Documento de texto.txt new file mode 100644 index 0000000..e69de29 diff --git a/SIG.Web/Pages/Fraccionamientos/FraccionamientoDetalle.aspx b/SIG.Web/Pages/Fraccionamientos/FraccionamientoDetalle.aspx index 4610655..7b69a95 100644 --- a/SIG.Web/Pages/Fraccionamientos/FraccionamientoDetalle.aspx +++ b/SIG.Web/Pages/Fraccionamientos/FraccionamientoDetalle.aspx @@ -498,33 +498,11 @@ - - Abrir en otra pestaña + CssClass="btn btn-default btn-sm"> + + Descargar GIS - -
- -
- - - - El GIS guardado no tiene una URL HTTP/HTTPS válida. - Use Administrar GIS para corregirla.
@@ -985,25 +963,15 @@
- - Archivo GIS * + -
- -
- - + accept=".gis" /> - Utilice una dirección HTTP o HTTPS que permita mostrarse dentro de la plataforma. + Seleccione un archivo con extensión .gis. + El nombre se asignará automáticamente al guardar.
@@ -1025,9 +993,8 @@ - + @@ -1314,35 +1281,7 @@