crear usuario externo

This commit is contained in:
Jorge Luis caudillo esparza
2026-07-14 19:19:38 -07:00
parent 6685ef63fb
commit 30f2f917a4
16 changed files with 1357 additions and 115 deletions

View File

@@ -0,0 +1,418 @@
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
SET XACT_ABORT ON;
GO
/*
Catálogos postales para el registro público.
Este script NO elimina, vacía ni recrea las tablas pobladas.
Solamente agrega columnas/índices faltantes y crea o actualiza
el procedimiento almacenado de consulta.
*/
IF OBJECT_ID(N'dbo.Gral_SAT_CP', N'U') IS NULL
THROW 51020, N'Falta la tabla poblada dbo.Gral_SAT_CP.', 1;
IF OBJECT_ID(N'dbo.Gral_SAT_Municipio', N'U') IS NULL
THROW 51021, N'Falta la tabla poblada dbo.Gral_SAT_Municipio.', 1;
IF OBJECT_ID(N'dbo.Gral_SAT_Localidad', N'U') IS NULL
THROW 51022, N'Falta la tabla poblada dbo.Gral_SAT_Localidad.', 1;
IF OBJECT_ID(N'dbo.Gral_SAT_Colonia', N'U') IS NULL
THROW 51023, N'Falta la tabla poblada dbo.Gral_SAT_Colonia.', 1;
IF OBJECT_ID(N'dbo.Gral_SAT_Estado', N'U') IS NULL
THROW 51024, N'Falta la tabla poblada dbo.Gral_SAT_Estado.', 1;
IF OBJECT_ID(N'dbo.Gral_SAT_Pais', N'U') IS NULL
THROW 51025, N'Falta la tabla poblada dbo.Gral_SAT_Pais.', 1;
GO
/* Borrado lógico incremental para los catálogos utilizados. */
DECLARE @Tablas TABLE ([Tabla] sysname NOT NULL PRIMARY KEY);
INSERT INTO @Tablas ([Tabla])
VALUES
(N'Gral_SAT_CP'),
(N'Gral_SAT_Municipio'),
(N'Gral_SAT_Localidad'),
(N'Gral_SAT_Colonia'),
(N'Gral_SAT_Estado'),
(N'Gral_SAT_Pais');
DECLARE @Tabla sysname;
DECLARE @Sql nvarchar(max);
DECLARE Catalogos CURSOR LOCAL FAST_FORWARD FOR SELECT [Tabla] FROM @Tablas ORDER BY [Tabla];
OPEN Catalogos;
FETCH NEXT FROM Catalogos INTO @Tabla;
WHILE @@FETCH_STATUS = 0
BEGIN
IF COL_LENGTH(N'dbo.' + @Tabla, N'Borrado') IS NULL
BEGIN
SET @Sql = N'ALTER TABLE [dbo].' + QUOTENAME(@Tabla)
+ N' ADD [Borrado] bit NOT NULL CONSTRAINT '
+ QUOTENAME(N'DF_' + @Tabla + N'_Borrado')
+ N' DEFAULT ((0)) WITH VALUES;';
EXEC sys.sp_executesql @Sql;
END;
IF COL_LENGTH(N'dbo.' + @Tabla, N'FechaBorrado') IS NULL
BEGIN
SET @Sql = N'ALTER TABLE [dbo].' + QUOTENAME(@Tabla)
+ N' ADD [FechaBorrado] datetime2(0) NULL;';
EXEC sys.sp_executesql @Sql;
END;
IF NOT EXISTS
(
SELECT 1
FROM sys.check_constraints
WHERE [parent_object_id] = OBJECT_ID(N'dbo.' + @Tabla)
AND [name] = N'CK_' + @Tabla + N'_BorradoLogico'
)
BEGIN
SET @Sql = N'ALTER TABLE [dbo].' + QUOTENAME(@Tabla)
+ N' ADD CONSTRAINT ' + QUOTENAME(N'CK_' + @Tabla + N'_BorradoLogico')
+ N' CHECK (([Borrado] = (0) AND [FechaBorrado] IS NULL)'
+ N' OR ([Borrado] = (1) AND [FechaBorrado] IS NOT NULL));';
EXEC sys.sp_executesql @Sql;
END;
FETCH NEXT FROM Catalogos INTO @Tabla;
END;
CLOSE Catalogos;
DEALLOCATE Catalogos;
GO
IF NOT EXISTS
(
SELECT 1 FROM sys.indexes
WHERE [object_id] = OBJECT_ID(N'dbo.Gral_SAT_CP')
AND [name] = N'IX_Gral_SAT_CP_BusquedaRegistro'
)
CREATE NONCLUSTERED INDEX [IX_Gral_SAT_CP_BusquedaRegistro]
ON [dbo].[Gral_SAT_CP] ([CP])
INCLUDE ([CveCP], [ClaveSATEstado], [ClaveSATMunicipio], [ClaveSATLocalidad])
WHERE [Borrado] = (0);
GO
IF NOT EXISTS
(
SELECT 1 FROM sys.indexes
WHERE [object_id] = OBJECT_ID(N'dbo.Gral_SAT_Colonia')
AND [name] = N'IX_Gral_SAT_Colonia_CP_Vigente'
)
CREATE NONCLUSTERED INDEX [IX_Gral_SAT_Colonia_CP_Vigente]
ON [dbo].[Gral_SAT_Colonia] ([CP])
INCLUDE ([CveColonia], [ClaveSATColonia], [NombreAsentamiento])
WHERE [Borrado] = (0);
GO
IF NOT EXISTS
(
SELECT 1 FROM sys.indexes
WHERE [object_id] = OBJECT_ID(N'dbo.Gral_SAT_Municipio')
AND [name] = N'IX_Gral_SAT_Municipio_Claves_Vigente'
)
CREATE NONCLUSTERED INDEX [IX_Gral_SAT_Municipio_Claves_Vigente]
ON [dbo].[Gral_SAT_Municipio] ([ClaveSATEstado], [ClaveSATMunicipio])
INCLUDE ([CveMunicipio], [Descripcion])
WHERE [Borrado] = (0);
GO
IF NOT EXISTS
(
SELECT 1 FROM sys.indexes
WHERE [object_id] = OBJECT_ID(N'dbo.Gral_SAT_Localidad')
AND [name] = N'IX_Gral_SAT_Localidad_Claves_Vigente'
)
CREATE NONCLUSTERED INDEX [IX_Gral_SAT_Localidad_Claves_Vigente]
ON [dbo].[Gral_SAT_Localidad] ([ClaveSATEstado], [ClaveSATLocalidad])
INCLUDE ([CveLocalidad], [NombreLocalidad])
WHERE [Borrado] = (0);
GO
/* Claves normalizadas utilizadas por el registro. */
IF OBJECT_ID(N'dbo.Clientes', N'U') IS NULL
THROW 51026, N'Falta la tabla dbo.Clientes.', 1;
IF COL_LENGTH(N'dbo.Clientes', N'CveLocalidad') IS NULL
ALTER TABLE [dbo].[Clientes] ADD [CveLocalidad] int NULL;
GO
IF OBJECT_ID(N'dbo.FK_Clientes_Gral_SAT_Pais', N'F') IS NULL
AND NOT EXISTS (SELECT 1 FROM [dbo].[Clientes] C LEFT JOIN [dbo].[Gral_SAT_Pais] P ON P.[CvePais]=C.[CvePais] WHERE C.[CvePais] IS NOT NULL AND P.[CvePais] IS NULL)
ALTER TABLE [dbo].[Clientes] WITH CHECK ADD CONSTRAINT [FK_Clientes_Gral_SAT_Pais] FOREIGN KEY ([CvePais]) REFERENCES [dbo].[Gral_SAT_Pais] ([CvePais]);
GO
IF OBJECT_ID(N'dbo.FK_Clientes_Gral_SAT_Estado_Normalizado', N'F') IS NULL
AND NOT EXISTS (SELECT 1 FROM [dbo].[Clientes] C LEFT JOIN [dbo].[Gral_SAT_Estado] E ON E.[CveEstado]=C.[CveEstado] WHERE C.[CveEstado] IS NOT NULL AND E.[CveEstado] IS NULL)
ALTER TABLE [dbo].[Clientes] WITH CHECK ADD CONSTRAINT [FK_Clientes_Gral_SAT_Estado_Normalizado] FOREIGN KEY ([CveEstado]) REFERENCES [dbo].[Gral_SAT_Estado] ([CveEstado]);
GO
IF OBJECT_ID(N'dbo.FK_Clientes_Gral_SAT_Municipio', N'F') IS NULL
AND NOT EXISTS (SELECT 1 FROM [dbo].[Clientes] C LEFT JOIN [dbo].[Gral_SAT_Municipio] M ON M.[CveMunicipio]=C.[CveMunicipio] WHERE C.[CveMunicipio] IS NOT NULL AND M.[CveMunicipio] IS NULL)
ALTER TABLE [dbo].[Clientes] WITH CHECK ADD CONSTRAINT [FK_Clientes_Gral_SAT_Municipio] FOREIGN KEY ([CveMunicipio]) REFERENCES [dbo].[Gral_SAT_Municipio] ([CveMunicipio]);
GO
IF OBJECT_ID(N'dbo.FK_Clientes_Gral_SAT_Localidad', N'F') IS NULL
ALTER TABLE [dbo].[Clientes] WITH CHECK ADD CONSTRAINT [FK_Clientes_Gral_SAT_Localidad] FOREIGN KEY ([CveLocalidad]) REFERENCES [dbo].[Gral_SAT_Localidad] ([CveLocalidad]);
GO
IF OBJECT_ID(N'dbo.FK_Clientes_Gral_SAT_Colonia', N'F') IS NULL
AND NOT EXISTS (SELECT 1 FROM [dbo].[Clientes] C LEFT JOIN [dbo].[Gral_SAT_Colonia] CO ON CO.[CveColonia]=C.[CveColonia] WHERE C.[CveColonia] IS NOT NULL AND CO.[CveColonia] IS NULL)
ALTER TABLE [dbo].[Clientes] WITH CHECK ADD CONSTRAINT [FK_Clientes_Gral_SAT_Colonia] FOREIGN KEY ([CveColonia]) REFERENCES [dbo].[Gral_SAT_Colonia] ([CveColonia]);
GO
CREATE OR ALTER PROCEDURE [dbo].[PA_Gral_CodigoPostalGet]
@CP varchar(5),
@CveUsuarioEjecuta int,
@ErrorMostrar varchar(max) OUTPUT,
@ErrorTipo int OUTPUT,
@Return bit OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SELECT @ErrorMostrar = NULL, @ErrorTipo = 0, @Return = 0;
SET @CP = LTRIM(RTRIM(@CP));
BEGIN TRY
IF LEN(@CP) <> 5 OR @CP LIKE '%[^0-9]%'
BEGIN
SET @ErrorMostrar = 'El código postal debe contener exactamente 5 dígitos.';
SET @ErrorTipo = 2;
RETURN;
END;
DECLARE
@CvePais int,
@Pais varchar(100),
@CveEstado int,
@Estado varchar(50),
@CveMunicipio int,
@Municipio varchar(100),
@CveLocalidad int,
@Localidad varchar(100);
SELECT TOP (1)
@CvePais = P.[CvePais],
@Pais = P.[Descripcion],
@CveEstado = E.[CveEstado],
@Estado = E.[NombreEstado],
@CveMunicipio = M.[CveMunicipio],
@Municipio = M.[Descripcion],
@CveLocalidad = L.[CveLocalidad],
@Localidad = L.[NombreLocalidad]
FROM [dbo].[Gral_SAT_CP] AS CP
INNER JOIN [dbo].[Gral_SAT_Estado] AS E
ON E.[ClaveSATEstado] = CP.[ClaveSATEstado]
AND E.[Borrado] = (0)
INNER JOIN [dbo].[Gral_SAT_Pais] AS P
ON P.[ClaveSATPais] = E.[ClaveSATPais]
AND P.[Borrado] = (0)
INNER JOIN [dbo].[Gral_SAT_Municipio] AS M
ON M.[ClaveSATEstado] = CP.[ClaveSATEstado]
AND M.[ClaveSATMunicipio] = CP.[ClaveSATMunicipio]
AND M.[Borrado] = (0)
LEFT JOIN [dbo].[Gral_SAT_Localidad] AS L
ON L.[ClaveSATEstado] = CP.[ClaveSATEstado]
AND L.[ClaveSATLocalidad] = CP.[ClaveSATLocalidad]
AND L.[Borrado] = (0)
WHERE CP.[CP] = @CP
AND CP.[Borrado] = (0);
IF @CvePais IS NULL OR @CveEstado IS NULL OR @CveMunicipio IS NULL
BEGIN
SET @ErrorMostrar = CONCAT(
'El código postal ', @CP,
' no existe o sus relaciones de país, estado y municipio están incompletas.');
SET @ErrorTipo = 2;
RETURN;
END;
SELECT
@CvePais AS [CvePais],
@Pais AS [Pais],
@CveEstado AS [CveEstado],
@Estado AS [Estado],
@CveMunicipio AS [CveMunicipio],
@Municipio AS [Municipio],
@CveLocalidad AS [CveLocalidad],
@Localidad AS [Localidad],
COALESCE(NULLIF(@Localidad, ''), @Municipio) AS [Ciudad];
SELECT
C.[CveColonia],
C.[NombreAsentamiento]
FROM [dbo].[Gral_SAT_Colonia] AS C
WHERE C.[CP] = @CP
AND C.[Borrado] = (0)
ORDER BY C.[NombreAsentamiento];
SET @Return = 1;
END TRY
BEGIN CATCH
SET @ErrorMostrar = 'Ocurrió un error al consultar el código postal; favor de contactar a Sistemas.';
SET @ErrorTipo = 1;
SET @Return = 0;
DECLARE @E nvarchar(2048) = CONCAT(
'Número: ', ERROR_NUMBER(),
'; Procedimiento: ', ERROR_PROCEDURE(),
'; Línea: ', ERROR_LINE(),
'; Detalle: ', ERROR_MESSAGE());
RAISERROR(N'%s', 10, 1, @E);
END CATCH;
END;
GO
CREATE OR ALTER PROCEDURE [dbo].[PA_Sis_RegistroClienteAdd]
@NombreUsuario varchar(100),
@HashContrasena varchar(500),
@CorreoElectronico varchar(255),
@RFC varchar(15),
@RazonSocial varchar(200),
@NombreComercial varchar(200) = NULL,
@Direccion varchar(90),
@NoExt varchar(20) = NULL,
@NoInt varchar(50) = NULL,
@Colonia varchar(100),
@Ciudad varchar(100),
@CveSATEstado int,
@Pais varchar(100) = 'México',
@CP varchar(5),
@Telefono varchar(25) = NULL,
@CvePais int,
@CveMunicipio int,
@CveLocalidad int = NULL,
@CveColonia int,
@HashToken char(64),
@HorasExpiracion int = 24,
@CveUsuarioEjecuta int,
@ErrorMostrar varchar(max) OUTPUT,
@ErrorTipo int OUTPUT,
@Return bit OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
SELECT @ErrorMostrar = NULL, @ErrorTipo = 0, @Return = 0;
BEGIN TRY
DECLARE @CveRolEmpresa int, @CveUsuario int, @CveCliente int;
DECLARE @NombreEstado varchar(50), @PaisCatalogo varchar(100);
DECLARE @CiudadCatalogo varchar(100), @ColoniaCatalogo varchar(100);
SET @NombreUsuario = LTRIM(RTRIM(@NombreUsuario));
SET @CorreoElectronico = LOWER(LTRIM(RTRIM(@CorreoElectronico)));
SET @RFC = UPPER(REPLACE(LTRIM(RTRIM(@RFC)), ' ', ''));
SET @CP = LTRIM(RTRIM(@CP));
SELECT @CveRolEmpresa = [CveRol]
FROM [dbo].[Sis_Roles]
WHERE [NombreRol] = 'Empresa' AND [Activo] = (1) AND [Borrado] = (0);
SELECT TOP (1)
@NombreEstado = LEFT(E.[NombreEstado], 35),
@PaisCatalogo = LEFT(P.[Descripcion], 50),
@CiudadCatalogo = LEFT(COALESCE(NULLIF(L.[NombreLocalidad], ''), M.[Descripcion]), 50),
@ColoniaCatalogo = LEFT(C.[NombreAsentamiento], 50)
FROM [dbo].[Gral_SAT_CP] AS CP
INNER JOIN [dbo].[Gral_SAT_Estado] AS E
ON E.[ClaveSATEstado] = CP.[ClaveSATEstado]
AND E.[CveEstado] = @CveSATEstado AND E.[Borrado] = (0)
INNER JOIN [dbo].[Gral_SAT_Pais] AS P
ON P.[ClaveSATPais] = E.[ClaveSATPais]
AND P.[CvePais] = @CvePais AND P.[Borrado] = (0)
INNER JOIN [dbo].[Gral_SAT_Municipio] AS M
ON M.[ClaveSATEstado] = CP.[ClaveSATEstado]
AND M.[ClaveSATMunicipio] = CP.[ClaveSATMunicipio]
AND M.[CveMunicipio] = @CveMunicipio AND M.[Borrado] = (0)
LEFT JOIN [dbo].[Gral_SAT_Localidad] AS L
ON L.[ClaveSATEstado] = CP.[ClaveSATEstado]
AND L.[ClaveSATLocalidad] = CP.[ClaveSATLocalidad] AND L.[Borrado] = (0)
INNER JOIN [dbo].[Gral_SAT_Colonia] AS C
ON C.[CP] = CP.[CP] AND C.[CveColonia] = @CveColonia AND C.[Borrado] = (0)
WHERE CP.[CP] = @CP AND CP.[Borrado] = (0)
AND (@CveLocalidad IS NULL OR L.[CveLocalidad] = @CveLocalidad);
IF @CveRolEmpresa IS NULL
BEGIN
SELECT @ErrorMostrar = 'No fue posible determinar el rol Empresa.', @ErrorTipo = 1;
RETURN;
END;
IF @NombreEstado IS NULL OR @PaisCatalogo IS NULL OR @CiudadCatalogo IS NULL OR @ColoniaCatalogo IS NULL
BEGIN
SELECT @ErrorMostrar = 'Los datos del código postal no coinciden con los catálogos vigentes. Actualiza el código postal.', @ErrorTipo = 2;
RETURN;
END;
SELECT @Pais = @PaisCatalogo, @Ciudad = @CiudadCatalogo, @Colonia = @ColoniaCatalogo;
IF EXISTS (SELECT 1 FROM [dbo].[Sis_Usuarios] WHERE [NombreUsuario] = @NombreUsuario)
OR EXISTS (SELECT 1 FROM [dbo].[Sis_Usuarios] WHERE LOWER([CorreoElectronico]) = @CorreoElectronico)
OR EXISTS (SELECT 1 FROM [dbo].[Clientes] WHERE UPPER(REPLACE(LTRIM(RTRIM([RFC])), ' ', '')) = @RFC)
BEGIN
SELECT @ErrorMostrar = 'Los datos ya fueron registrados por otra solicitud. Actualiza la página e inténtalo nuevamente.', @ErrorTipo = 2;
RETURN;
END;
BEGIN TRANSACTION;
INSERT INTO [dbo].[Sis_Usuarios]
([NombreUsuario], [HashContrasena], [CorreoElectronico], [CveRol], [Activo], [DebeCambiarContrasena], [CorreoVerificado])
VALUES (@NombreUsuario, @HashContrasena, @CorreoElectronico, @CveRolEmpresa, (0), (0), (0));
SET @CveUsuario = CONVERT(int, SCOPE_IDENTITY());
INSERT INTO [dbo].[Clientes]
([Nombre], [NombreComercial], [Direccion], [NoExt], [NoInt], [Colonia],
[Ciudad], [Estado], [CveSATEstado], [Pais], [RFC], [CP], [Telefono],
[Mail], [Estatus], [Fecha], [CveUsuario], [FechaIng],
[CvePais], [CveEstado], [CveMunicipio], [CveLocalidad], [CveColonia])
VALUES
(@RazonSocial, NULLIF(@NombreComercial, ''), @Direccion, NULLIF(@NoExt, ''),
NULLIF(@NoInt, ''), @Colonia, @Ciudad, @NombreEstado, @CveSATEstado,
@Pais, @RFC, @CP, NULLIF(@Telefono, ''), @CorreoElectronico, (0),
CONVERT(date, GETDATE()), @CveUsuario, GETDATE(),
@CvePais, @CveSATEstado, @CveMunicipio, @CveLocalidad, @CveColonia);
SET @CveCliente = CONVERT(int, SCOPE_IDENTITY());
INSERT INTO [dbo].[Sis_UsuariosClientes]
([CveUsuario], [CveCliente], [Orden], [EsPrincipal], [Activo], [CveUsuarioRegistro])
VALUES (@CveUsuario, @CveCliente, (1), (1), (1), @CveUsuario);
INSERT INTO [dbo].[Sis_UsuariosVerificacionesCorreo]
([CveUsuario], [CveClienteRegistro], [HashToken], [FechaExpiracion])
VALUES (@CveUsuario, @CveCliente, @HashToken, DATEADD(HOUR, @HorasExpiracion, SYSDATETIME()));
COMMIT TRANSACTION;
SELECT @CveUsuario AS [CveUsuario], @CveCliente AS [CveCliente];
SET @Return = 1;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
SELECT @ErrorMostrar = 'Ocurrió un error al crear el registro; favor de contactar a Sistemas.', @ErrorTipo = 1, @Return = 0;
DECLARE @E nvarchar(2048) = CONCAT('Número: ', ERROR_NUMBER(), '; Procedimiento: ', ERROR_PROCEDURE(), '; Línea: ', ERROR_LINE(), '; Detalle: ', ERROR_MESSAGE());
RAISERROR(N'%s', 10, 1, @E);
END CATCH;
END;
GO
/*
Se registran para limpieza, pero la limpieza física queda deshabilitada.
Habilitarla posteriormente requiere autorización expresa.
*/
IF OBJECT_ID(N'dbo.Sis_ConfigurarLimpieza', N'P') IS NOT NULL
BEGIN
EXEC [dbo].[Sis_ConfigurarLimpieza] @Esquema=N'dbo', @Tabla=N'Gral_SAT_CP', @DiasRetencion=365, @TamanoLote=500, @Habilitada=0;
EXEC [dbo].[Sis_ConfigurarLimpieza] @Esquema=N'dbo', @Tabla=N'Gral_SAT_Municipio', @DiasRetencion=365, @TamanoLote=500, @Habilitada=0;
EXEC [dbo].[Sis_ConfigurarLimpieza] @Esquema=N'dbo', @Tabla=N'Gral_SAT_Localidad', @DiasRetencion=365, @TamanoLote=500, @Habilitada=0;
EXEC [dbo].[Sis_ConfigurarLimpieza] @Esquema=N'dbo', @Tabla=N'Gral_SAT_Colonia', @DiasRetencion=365, @TamanoLote=500, @Habilitada=0;
EXEC [dbo].[Sis_ConfigurarLimpieza] @Esquema=N'dbo', @Tabla=N'Gral_SAT_Estado', @DiasRetencion=365, @TamanoLote=500, @Habilitada=0;
EXEC [dbo].[Sis_ConfigurarLimpieza] @Esquema=N'dbo', @Tabla=N'Gral_SAT_Pais', @DiasRetencion=365, @TamanoLote=500, @Habilitada=0;
END;
GO

View File

@@ -1,3 +1,5 @@
using System;
using System.Configuration;
using SIG.Web.AppCode.Contracts.Database;
using SIG.Web.AppCode.Contracts.Search;
using SIG.Web.AppCode.Contracts.Security;
@@ -17,6 +19,7 @@ namespace SIG.Web.AppCode.Configuration
public static IDbConnectionFactory DbConnectionFactory { get; private set; }
public static IDbExecutor DbExecutor { get; private set; }
public static IPasswordHasher PasswordHasher { get; private set; }
public static ICaptchaService CaptchaService { get; private set; }
public static IUserRepository UserRepository { get; private set; }
public static IUserAdministrationRepository UserAdministrationRepository { get; private set; }
public static IPublicRegistrationService PublicRegistrationService { get; private set; }
@@ -34,6 +37,10 @@ namespace SIG.Web.AppCode.Configuration
DbExecutor = new DbExecutor(DbConnectionFactory);
PasswordHasher = new PasswordHasher();
CaptchaService = new RecaptchaV2Service(
ReadBooleanSetting("RecaptchaEnabled", "SIG_RECAPTCHA_ENABLED", false),
ReadSetting("RecaptchaSiteKey", "SIG_RECAPTCHA_SITE_KEY"),
ReadSetting("RecaptchaSecretKey", "SIG_RECAPTCHA_SECRET_KEY"));
UserRepository = new UserRepository(DbExecutor);
UserAdministrationRepository = new UserAdministrationRepository(DbExecutor);
@@ -55,5 +62,21 @@ namespace SIG.Web.AppCode.Configuration
UserAdministrationRepository,
PasswordHasher,
new SmtpEmailSender());
} }
}
private static string ReadSetting(string appSettingName, string environmentVariableName)
{
string environmentValue = Environment.GetEnvironmentVariable(environmentVariableName);
return string.IsNullOrWhiteSpace(environmentValue)
? ConfigurationManager.AppSettings[appSettingName]
: environmentValue;
}
private static bool ReadBooleanSetting(string appSettingName, string environmentVariableName, bool defaultValue)
{
bool value;
string configuredValue = ReadSetting(appSettingName, environmentVariableName);
return bool.TryParse(configuredValue, out value) ? value : defaultValue;
}
}
}

View File

@@ -0,0 +1,12 @@
using SIG.Web.AppCode.Models.Common;
namespace SIG.Web.AppCode.Contracts.Security
{
public interface ICaptchaService
{
bool IsEnabled { get; }
bool IsConfigured { get; }
string SiteKey { get; }
DbResult<bool> Verify(string responseToken, string remoteIp, string expectedHostName);
}
}

View File

@@ -8,6 +8,7 @@ namespace SIG.Web.AppCode.Contracts.Security
{
DbResult<bool> Validate(PublicRegistrationDraft draft, string domainName);
DbResult<List<StateOptionModel>> ListStates(string domainName);
DbResult<PostalCodeLookupModel> GetPostalCode(string postalCode, string domainName);
DbResult<bool> Register(PublicRegistrationDraft draft, string verificationBaseUrl, string domainName);
DbResult<bool> Verify(string token, string domainName);
}

View File

@@ -10,6 +10,7 @@ namespace SIG.Web.AppCode.Contracts.Security
DbResult<UserAdministrationModel> GetUser(int userId, int executingUser, string domainName);
DbResult<List<RoleOptionModel>> ListRoles(int executingUser, string domainName);
DbResult<List<StateOptionModel>> ListStates(string domainName);
DbResult<PostalCodeLookupModel> GetPostalCode(string postalCode, string domainName);
DbResult<int> SaveUser(UserAdministrationModel user, string passwordHash, int executingUser, string domainName);
DbResult<bool> DeleteUser(int userId, int executingUser, string domainName);
DbResult<List<UserClientModel>> ListUserClients(int userId, int executingUser, string domainName);

View File

@@ -20,6 +20,31 @@ namespace SIG.Web.AppCode.Models.Security
public string Pais { get; set; }
public string CP { get; set; }
public string Telefono { get; set; }
public int CvePais { get; set; }
public int CveMunicipio { get; set; }
public int CveLocalidad { get; set; }
public int CveColonia { get; set; }
}
public sealed class PostalCodeLookupModel
{
public PostalCodeLookupModel() { Colonias = new System.Collections.Generic.List<NeighborhoodOptionModel>(); }
public int CvePais { get; set; }
public string Pais { get; set; }
public int CveEstado { get; set; }
public string Estado { get; set; }
public int CveMunicipio { get; set; }
public string Municipio { get; set; }
public int CveLocalidad { get; set; }
public string Localidad { get; set; }
public string Ciudad { get; set; }
public System.Collections.Generic.List<NeighborhoodOptionModel> Colonias { get; set; }
}
public sealed class NeighborhoodOptionModel
{
public int CveColonia { get; set; }
public string NombreAsentamiento { get; set; }
}
public sealed class UserAdministrationModel

View File

@@ -26,6 +26,29 @@ namespace SIG.Web.AppCode.Repositories.Security
public DbResult<List<StateOptionModel>> ListStates(string domainName)
{ return QueryList<StateOptionModel>("dbo.PA_Gral_EstadoList", 0, domainName, null); }
public DbResult<PostalCodeLookupModel> GetPostalCode(string postalCode, string domainName)
{
return _db.Execute(domainName, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, "dbo.PA_Gral_CodigoPostalGet", 0);
StoredProcedureCommand.AddInput(command, "@CP", postalCode, DbType.String);
PostalCodeLookupModel value;
using (IDataReader reader = command.ExecuteReader())
{
value = reader.ToObject<PostalCodeLookupModel>();
if (value != null && reader.NextResult())
value.Colonias = reader.ToList<NeighborhoodOptionModel>();
}
StoredProcedureCommand.EnsureSuccess(command);
return value;
}
});
}
public DbResult<int> SaveUser(UserAdministrationModel user, string passwordHash, int executingUser, string domainName)
{
return _db.Execute(domainName, connection =>
@@ -94,6 +117,10 @@ namespace SIG.Web.AppCode.Repositories.Security
StoredProcedureCommand.AddInput(c, "@Pais", draft.Pais, DbType.String);
StoredProcedureCommand.AddInput(c, "@CP", draft.CP, DbType.String);
StoredProcedureCommand.AddInput(c, "@Telefono", draft.Telefono, DbType.String);
StoredProcedureCommand.AddInput(c, "@CvePais", draft.CvePais, DbType.Int32);
StoredProcedureCommand.AddInput(c, "@CveMunicipio", draft.CveMunicipio, DbType.Int32);
StoredProcedureCommand.AddInput(c, "@CveLocalidad", draft.CveLocalidad <= 0 ? (object)null : draft.CveLocalidad, DbType.Int32);
StoredProcedureCommand.AddInput(c, "@CveColonia", draft.CveColonia, DbType.Int32);
StoredProcedureCommand.AddInput(c, "@HashToken", tokenHash, DbType.StringFixedLength);
StoredProcedureCommand.AddInput(c, "@HorasExpiracion", 24, DbType.Int32);
});

View File

@@ -22,6 +22,7 @@ namespace SIG.Web.AppCode.Services.Security
}
public DbResult<List<StateOptionModel>> ListStates(string domainName) { return _repository.ListStates(domainName); }
public DbResult<PostalCodeLookupModel> GetPostalCode(string postalCode, string domainName) { return _repository.GetPostalCode(postalCode, domainName); }
public DbResult<bool> Register(PublicRegistrationDraft draft, string verificationBaseUrl, string domainName)
{
@@ -48,7 +49,7 @@ namespace SIG.Web.AppCode.Services.Security
if (d == null) return "Captura los datos del registro.";
if (string.IsNullOrWhiteSpace(d.NombreUsuario) || string.IsNullOrWhiteSpace(d.CorreoElectronico) || string.IsNullOrWhiteSpace(d.Contrasena)) return "Completa los datos de usuario.";
if (d.Contrasena.Length < 8) return "La contraseña debe tener al menos 8 caracteres.";
if (string.IsNullOrWhiteSpace(d.RFC) || string.IsNullOrWhiteSpace(d.RazonSocial) || string.IsNullOrWhiteSpace(d.Direccion) || string.IsNullOrWhiteSpace(d.Colonia) || string.IsNullOrWhiteSpace(d.Ciudad) || d.CveSATEstado <= 0) return "Completa los datos obligatorios de la empresa.";
if (string.IsNullOrWhiteSpace(d.RFC) || string.IsNullOrWhiteSpace(d.RazonSocial) || string.IsNullOrWhiteSpace(d.Direccion) || string.IsNullOrWhiteSpace(d.Colonia) || string.IsNullOrWhiteSpace(d.Ciudad) || d.CveSATEstado <= 0 || d.CvePais <= 0 || d.CveMunicipio <= 0 || d.CveColonia <= 0) return "Completa y valida los datos obligatorios de la empresa y su código postal.";
return null;
}

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using Newtonsoft.Json;
using SIG.Web.AppCode.Contracts.Security;
using SIG.Web.AppCode.Models.Common;
namespace SIG.Web.AppCode.Services.Security
{
public sealed class RecaptchaV2Service : ICaptchaService
{
private const string VerifyUrl = "https://www.google.com/recaptcha/api/siteverify";
private readonly bool _enabled;
private readonly string _secretKey;
public RecaptchaV2Service(bool enabled, string siteKey, string secretKey)
{
_enabled = enabled;
SiteKey = (siteKey ?? string.Empty).Trim();
_secretKey = (secretKey ?? string.Empty).Trim();
}
public bool IsEnabled { get { return _enabled; } }
public bool IsConfigured { get { return SiteKey.Length > 0 && _secretKey.Length > 0; } }
public string SiteKey { get; private set; }
public DbResult<bool> Verify(string responseToken, string remoteIp, string expectedHostName)
{
if (!IsEnabled)
return DbResult<bool>.Ok(true);
if (!IsConfigured)
return DbResult<bool>.Fail("La validación No soy un robot todavía no está configurada. Contacta a Sistemas.", "Faltan RecaptchaSiteKey o RecaptchaSecretKey.", null);
if (string.IsNullOrWhiteSpace(responseToken))
return DbResult<bool>.Fail("Confirma que no eres un robot antes de crear la cuenta.", "Token reCAPTCHA vacío.", null);
try
{
string form = "secret=" + Uri.EscapeDataString(_secretKey)
+ "&response=" + Uri.EscapeDataString(responseToken);
if (!string.IsNullOrWhiteSpace(remoteIp))
form += "&remoteip=" + Uri.EscapeDataString(remoteIp);
byte[] body = Encoding.UTF8.GetBytes(form);
var request = (HttpWebRequest)WebRequest.Create(VerifyUrl);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
request.ContentLength = body.Length;
request.Timeout = 10000;
request.ReadWriteTimeout = 10000;
using (Stream stream = request.GetRequestStream()) stream.Write(body, 0, body.Length);
string json;
using (var response = (HttpWebResponse)request.GetResponse())
using (var reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
json = reader.ReadToEnd();
VerificationResponse verification = JsonConvert.DeserializeObject<VerificationResponse>(json);
if (verification == null || !verification.Success)
return DbResult<bool>.Fail("No fue posible validar que no eres un robot. Inténtalo nuevamente.", "reCAPTCHA rechazado: " + JoinCodes(verification), null);
if (!string.IsNullOrWhiteSpace(expectedHostName)
&& !string.Equals(expectedHostName, verification.HostName, StringComparison.OrdinalIgnoreCase))
return DbResult<bool>.Fail("La validación No soy un robot no corresponde a este sitio. Inténtalo nuevamente.", "Hostname reCAPTCHA inesperado.", null);
return DbResult<bool>.Ok(true);
}
catch (Exception ex)
{
return DbResult<bool>.Fail("El servicio No soy un robot no está disponible en este momento. Inténtalo nuevamente.", ex.Message, ex);
}
}
private static string JoinCodes(VerificationResponse response)
{
return response == null || response.ErrorCodes == null ? "respuesta inválida" : string.Join(",", response.ErrorCodes);
}
private sealed class VerificationResponse
{
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("hostname")]
public string HostName { get; set; }
[JsonProperty("error-codes")]
public List<string> ErrorCodes { get; set; }
}
}
}

View File

@@ -1,4 +1,4 @@
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="SIG.Web.Login" %>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="SIG.Web.Login" CodePage="65001" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
@@ -155,16 +155,24 @@
}
.login-card-icon {
min-height: 92px;
margin-bottom: 14px;
display: flex;
align-items: center;
justify-content: center;
gap: 30px;
}
.login-card-icon img {
width: 148px;
max-width: 100%;
height: auto;
display: inline-block;
display: block;
object-fit: contain;
}
.login-card-icon .water-logo { width: 165px; }
.login-card-icon .developer-logo { width: 78px; }
.login-card-title {
margin: 0;
font-size: 34px;
@@ -214,11 +222,18 @@
.input-icon > i {
position: absolute;
left: 17px;
top: 50%;
transform: translateY(-50%);
top: 0;
bottom: 0;
width: 22px;
display: flex;
align-items: center;
justify-content: center;
margin: 0;
color: #7a8ca9;
font-size: 20px;
line-height: 1;
z-index: 2;
pointer-events: none;
}
.input-icon .form-control {
@@ -258,36 +273,41 @@
}
.login-options {
margin-top: 18px;
margin-top: 20px;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
align-items: center;
gap: 14px 22px;
color: #173b70;
font-size: 14px;
}
.login-links {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
color: #173b70;
font-size: 15px;
justify-content: flex-end;
gap: 22px;
}
.login-options a {
color: #0c63c7;
text-decoration: none;
font-weight: 600;
line-height: 1.35;
text-align: right;
}
.login-options a:hover {
text-decoration: underline;
}
.login-options a:hover { text-decoration: underline; }
.remember-label {
.remember-check {
display: inline-flex;
align-items: center;
gap: 8px;
margin: 0;
white-space: nowrap;
font-weight: 500;
}
.remember-label input {
margin: 0;
}
.remember-check input { margin: 0 7px 0 0; vertical-align: middle; }
.remember-check label { margin: 0; font-weight: 500; cursor: pointer; }
.login-footer-note {
position: fixed;
@@ -361,10 +381,9 @@
font-size: 28px;
}
.login-options {
flex-direction: column;
align-items: flex-start;
}
.login-options { grid-template-columns: 1fr; align-items: stretch; }
.login-links { justify-content: space-between; gap: 14px; }
.login-options a { text-align: left; }
.login-footer-note {
position: static;
@@ -401,8 +420,8 @@
<div class="login-card">
<div class="login-card-header">
<div class="login-card-icon">
<img src="<%= ResolveUrl("~/Imagenes/logo-agua-hermosillo-blancol.png") %>" alt="Agua de Hermosillo" />
<img src="<%= ResolveUrl("~/Imagenes/icono-desarrolladores-azul.png") %>" alt="Desarrolladores" />
<img class="water-logo" src="<%= ResolveUrl("~/Imagenes/logo-agua-hermosillo-blancol.png") %>" alt="Agua de Hermosillo" />
<img class="developer-logo" src="<%= ResolveUrl("~/Imagenes/icono-desarrolladores-azul_recorte.png") %>" alt="Desarrolladores" />
</div>
<h2 class="login-card-title">
@@ -421,7 +440,7 @@
<div class="login-form-group">
<label for="<%= TxtUsuario.ClientID %>">Usuario</label>
<div class="input-icon">
<i class="fa fa-user-o"></i>
<i class="fa fa-user"></i>
<asp:TextBox ID="TxtUsuario" runat="server" CssClass="form-control" placeholder="Ingrese su usuario"></asp:TextBox>
</div>
</div>
@@ -437,13 +456,11 @@
<asp:Button ID="BtnLogin" runat="server" Text="Iniciar sesión" CssClass="btn btn-login-main" OnClick="BtnLogin_Click" />
<div class="login-options">
<label class="remember-label">
<asp:CheckBox ID="ChkRecordarme" runat="server" />
<span>Recordarme</span>
</label>
<a href="javascript:void(0);">¿Olvidaste tu contraseña?</a>
<a href="<%= ResolveUrl("~/Pages/Security/RegistroCliente.aspx") %>">Registrar mi empresa</a>
<asp:CheckBox ID="ChkRecordarme" runat="server" CssClass="remember-check" Text="Recordarme" />
<div class="login-links">
<a href="javascript:void(0);">¿Olvidaste tu contraseña?</a>
<a href="<%= ResolveUrl("~/Pages/Security/RegistroCliente.aspx") %>">Registrar mi empresa</a>
</div>
</div>
</asp:Panel>
</div>

View File

@@ -1,37 +1,319 @@
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="RegistroCliente.aspx.cs" Inherits="SIG.Web.Pages.Security.RegistroCliente" ViewStateEncryptionMode="Always" %>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="RegistroCliente.aspx.cs" Inherits="SIG.Web.Pages.Security.RegistroCliente" ViewStateEncryptionMode="Always" CodePage="65001" %>
<%@ Import Namespace="System.Web.Optimization" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="es">
<head runat="server">
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Crear cuenta - Plataforma de Desarrolladores</title>
<%: Styles.Render("~/Metronic/assets/global/css/cssV1") %>
<style>.registration-page{background:#eef3f8;min-height:100vh;padding:35px 15px}.registration-card{max-width:920px;margin:auto;background:#fff;padding:30px;border-radius:8px;box-shadow:0 8px 28px rgba(0,0,0,.12)}.steps{display:flex;margin-bottom:28px}.step{flex:1;padding:12px;text-align:center;border-bottom:4px solid #cbd5df}.step.active{color:#167ac6;border-color:#167ac6;font-weight:700}.field{margin-bottom:15px}.actions{display:flex;justify-content:space-between;margin-top:25px}.required:after{content:' *';color:#d9534f}</style>
<style>
html, body { min-height: 100%; }
body.registration-page {
min-height: 100vh;
margin: 0;
padding: 42px 18px;
position: relative;
font-family: "Open Sans", "Segoe UI", Arial, sans-serif;
color: #173b70;
background: #123f6d url('<%= ResolveUrl("~/Imagenes/login-fondo-cerro-campana.jpg") %>') center center / cover fixed no-repeat;
}
body.registration-page::before {
content: "";
position: fixed;
inset: 0;
background: linear-gradient(135deg, rgba(3, 37, 72, .92), rgba(5, 78, 132, .75) 52%, rgba(3, 40, 77, .9));
pointer-events: none;
}
.registration-shell { position: relative; z-index: 1; max-width: 1080px; margin: 0 auto; }
.registration-card {
overflow: hidden;
background: #fff;
border-radius: 24px;
box-shadow: 0 26px 70px rgba(1, 24, 48, .38);
}
.registration-header {
min-height: 215px;
padding: 34px 42px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 32px;
color: #fff;
position: relative;
background:
linear-gradient(100deg, rgba(5, 53, 102, .98), rgba(11, 103, 184, .84)),
url('<%= ResolveUrl("~/Imagenes/cerro-campana-referencia.jpg") %>') center 47% / cover no-repeat;
}
.registration-header::after {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(circle at 82% 20%, rgba(35, 181, 255, .30), transparent 34%);
pointer-events: none;
}
.registration-brand, .registration-symbol { position: relative; z-index: 1; }
.registration-brand img { width: 205px; height: auto; margin-bottom: 20px; }
.registration-brand h1 { margin: 0 0 8px; font-size: 34px; line-height: 1.1; font-weight: 700; }
.registration-brand p { margin: 0; max-width: 650px; color: rgba(255,255,255,.90); font-size: 16px; line-height: 1.55; }
.registration-symbol {
flex: 0 0 112px;
width: 112px;
height: 112px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 28px;
background: rgba(255,255,255,.96);
box-shadow: 0 12px 30px rgba(0,0,0,.18);
}
.registration-symbol img { width: 78px; height: auto; }
.registration-content { padding: 34px 42px 38px; }
.steps { display: flex; margin-bottom: 30px; position: relative; }
.steps::before { content: ""; position: absolute; left: 25%; right: 25%; top: 21px; height: 3px; background: #dbe4ee; }
.step { flex: 1; position: relative; z-index: 1; text-align: center; color: #7a8ca3; font-size: 14px; font-weight: 600; }
.step-number {
width: 44px;
height: 44px;
margin: 0 auto 9px;
display: flex;
align-items: center;
justify-content: center;
border: 3px solid #dbe4ee;
border-radius: 50%;
background: #fff;
color: #7a8ca3;
font-size: 16px;
font-weight: 700;
}
.step.active { color: #0b67c2; }
.step.active .step-number { color: #fff; border-color: #1686e2; background: linear-gradient(135deg, #0c63bc, #1b98ef); box-shadow: 0 6px 16px rgba(15,112,205,.28); }
.registration-message { display: block; margin: 0 0 24px; padding: 13px 16px; border: 0; border-left: 4px solid #d9534f; border-radius: 7px !important; }
.form-section-title { margin: 0 0 6px; color: #173b70; font-size: 22px; font-weight: 700; }
.form-section-help { margin: 0 0 24px; color: #718096; }
.field { margin-bottom: 20px; }
.field label { display: block; margin-bottom: 8px; color: #27496f; font-size: 14px; font-weight: 700; }
.field .form-control {
height: 50px;
padding: 12px 14px;
border: 1px solid #d5dfeb;
border-radius: 10px !important;
background: #fff;
color: #173b70;
box-shadow: none;
}
.field .form-control:focus { border-color: #188be3; box-shadow: 0 0 0 3px rgba(24,139,227,.12); }
.required::after { content: " *"; color: #d9534f; }
.field .form-control[readonly], .field select.form-control:disabled { background: #eef4fa; color: #365a7d; opacity: 1; }
.postal-code-control { display: flex; align-items: stretch; gap: 8px; }
.postal-code-control .form-control { min-width: 0; flex: 1; }
.postal-refresh {
width: 50px;
min-width: 50px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 0;
border-radius: 10px !important;
background: #1587e2;
color: #fff;
font-size: 17px;
box-shadow: 0 5px 12px rgba(12,99,188,.20);
}
.postal-refresh:hover, .postal-refresh:focus { background: #0c63bc; color: #fff; text-decoration: none; }
.postal-status { display: block; margin-top: 8px; color: #718096; font-size: 12px; line-height: 1.4; }
.postal-status.success { color: #278251; }
.actions { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin-top: 12px; padding-top: 22px; border-top: 1px solid #e9eef4; }
.actions .btn { min-width: 140px; padding: 12px 20px; border-radius: 9px !important; font-weight: 700; }
.actions .btn.blue, .actions .btn.green { border: 0; background: linear-gradient(90deg, #0c63bc, #1587e2) !important; box-shadow: 0 7px 16px rgba(12,99,188,.22); }
.back-link { color: #526b87 !important; background: #eef3f7 !important; border: 0 !important; }
.captcha-section { margin: 4px 0 22px; padding: 18px; border: 1px solid #dce6f0; border-radius: 10px; background: #f8fbfe; }
.captcha-title { display: block; margin-bottom: 12px; color: #27496f; font-size: 14px; font-weight: 700; }
.captcha-configuration { display: block; margin: 0; color: #a94442; line-height: 1.5; }
.registration-security { margin-top: 16px; text-align: center; color: rgba(255,255,255,.82); font-size: 13px; }
@media (max-width: 767px) {
body.registration-page { padding: 16px 10px; }
.registration-card { border-radius: 17px; }
.registration-header { min-height: 0; padding: 27px 24px; }
.registration-brand img { width: 165px; }
.registration-brand h1 { font-size: 27px; }
.registration-symbol { display: none; }
.registration-content { padding: 27px 20px 28px; }
.actions { align-items: stretch; flex-direction: column-reverse; }
.actions .btn { width: 100%; }
}
</style>
</head>
<body class="registration-page">
<form id="form1" runat="server">
<asp:ScriptManager ID="SM" runat="server" />
<div class="registration-card">
<h2>Registro de empresa</h2><p>Los datos se guardarán hasta concluir ambas validaciones.</p>
<asp:UpdatePanel ID="UpRegistro" runat="server" UpdateMode="Conditional"><ContentTemplate>
<div class="steps"><div id="StepUser" runat="server" class="step active">1. Datos de usuario</div><div id="StepCompany" runat="server" class="step">2. Datos de empresa</div></div>
<asp:Label ID="LblMessage" runat="server" CssClass="alert alert-danger" Visible="false" />
<asp:MultiView ID="MvRegistration" runat="server" ActiveViewIndex="0">
<asp:View ID="ViewUser" runat="server">
<div class="row"><div class="col-md-6 field"><label class="required">Usuario</label><asp:TextBox ID="TxtUsername" runat="server" CssClass="form-control" MaxLength="100" /></div><div class="col-md-6 field"><label class="required">Correo electrónico</label><asp:TextBox ID="TxtEmail" runat="server" CssClass="form-control" TextMode="Email" MaxLength="255" /></div></div>
<div class="row"><div class="col-md-6 field"><label class="required">Contraseña</label><asp:TextBox ID="TxtPassword" runat="server" CssClass="form-control" TextMode="Password" /></div><div class="col-md-6 field"><label class="required">Confirmar contraseña</label><asp:TextBox ID="TxtConfirmPassword" runat="server" CssClass="form-control" TextMode="Password" /></div></div>
<div class="actions"><a href="../../Login.aspx" class="btn default">Volver al acceso</a><asp:Button ID="BtnNext" runat="server" CssClass="btn blue" Text="Continuar" OnClick="BtnNext_Click" /></div>
</asp:View>
<asp:View ID="ViewCompany" runat="server">
<div class="row"><div class="col-md-6 field"><label class="required">Razón social</label><asp:TextBox ID="TxtBusinessName" runat="server" CssClass="form-control" MaxLength="200" /></div><div class="col-md-6 field"><label>Nombre comercial</label><asp:TextBox ID="TxtTradeName" runat="server" CssClass="form-control" MaxLength="200" /></div></div>
<div class="row"><div class="col-md-4 field"><label class="required">RFC</label><asp:TextBox ID="TxtRfc" runat="server" CssClass="form-control text-uppercase" MaxLength="15" /></div><div class="col-md-8 field"><label class="required">Dirección</label><asp:TextBox ID="TxtAddress" runat="server" CssClass="form-control" MaxLength="90" /></div></div>
<div class="row"><div class="col-md-3 field"><label>No. exterior</label><asp:TextBox ID="TxtExterior" runat="server" CssClass="form-control" MaxLength="20" /></div><div class="col-md-3 field"><label>No. interior</label><asp:TextBox ID="TxtInterior" runat="server" CssClass="form-control" MaxLength="50" /></div><div class="col-md-6 field"><label class="required">Colonia</label><asp:TextBox ID="TxtNeighborhood" runat="server" CssClass="form-control" MaxLength="50" /></div></div>
<div class="row"><div class="col-md-4 field"><label class="required">Ciudad</label><asp:TextBox ID="TxtCity" runat="server" CssClass="form-control" MaxLength="50" /></div><div class="col-md-4 field"><label class="required">Estado</label><asp:DropDownList ID="DdlState" runat="server" CssClass="form-control" /></div><div class="col-md-4 field"><label>País</label><asp:TextBox ID="TxtCountry" runat="server" CssClass="form-control" MaxLength="50" Text="México" /></div></div>
<div class="row"><div class="col-md-4 field"><label>Código postal</label><asp:TextBox ID="TxtPostalCode" runat="server" CssClass="form-control" MaxLength="10" /></div><div class="col-md-4 field"><label>Teléfono</label><asp:TextBox ID="TxtPhone" runat="server" CssClass="form-control" MaxLength="25" /></div></div>
<div class="actions"><asp:Button ID="BtnBack" runat="server" CssClass="btn default" Text="Regresar" OnClick="BtnBack_Click" CausesValidation="false" /><asp:Button ID="BtnRegister" runat="server" CssClass="btn green" Text="Crear cuenta" OnClick="BtnRegister_Click" /></div>
</asp:View>
</asp:MultiView>
</ContentTemplate></asp:UpdatePanel>
</div>
<asp:ScriptManager ID="SM" runat="server" />
<div class="registration-shell">
<div class="registration-card">
<header class="registration-header">
<div class="registration-brand">
<img src="<%= ResolveUrl("~/Imagenes/logo-agua-hermosillo-blanco.png") %>" alt="Agua de Hermosillo" />
<h1>Registro de empresa</h1>
<p>Crea el acceso de tu empresa a la Plataforma de Desarrolladores. La información se guardará únicamente después de completar y validar ambos pasos.</p>
</div>
<div class="registration-symbol">
<img src="<%= ResolveUrl("~/Imagenes/icono-desarrolladores-azul_recorte.png") %>" alt="Plataforma de Desarrolladores" />
</div>
</header>
<main class="registration-content">
<asp:UpdatePanel ID="UpRegistro" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<div class="steps">
<div id="StepUser" runat="server" class="step active"><span class="step-number">1</span><span>Datos de usuario</span></div>
<div id="StepCompany" runat="server" class="step"><span class="step-number">2</span><span>Datos de empresa</span></div>
</div>
<asp:Label ID="LblMessage" runat="server" CssClass="alert alert-danger registration-message" Visible="false" />
<asp:MultiView ID="MvRegistration" runat="server" ActiveViewIndex="0">
<asp:View ID="ViewUser" runat="server">
<h2 class="form-section-title">Datos para iniciar sesión</h2>
<p class="form-section-help">El correo será utilizado para verificar y activar la cuenta.</p>
<div class="row">
<div class="col-md-6 field"><label class="required">Usuario</label><asp:TextBox ID="TxtUsername" runat="server" CssClass="form-control" MaxLength="100" /></div>
<div class="col-md-6 field"><label class="required">Correo electrónico</label><asp:TextBox ID="TxtEmail" runat="server" CssClass="form-control" TextMode="Email" MaxLength="255" /></div>
</div>
<div class="row">
<div class="col-md-6 field"><label class="required">Contraseña</label><asp:TextBox ID="TxtPassword" runat="server" CssClass="form-control" TextMode="Password" /></div>
<div class="col-md-6 field"><label class="required">Confirmar contraseña</label><asp:TextBox ID="TxtConfirmPassword" runat="server" CssClass="form-control" TextMode="Password" /></div>
</div>
<div class="actions"><a href="../../Login.aspx" class="btn back-link">Volver al acceso</a><asp:Button ID="BtnNext" runat="server" CssClass="btn blue" Text="Continuar" OnClick="BtnNext_Click" /></div>
</asp:View>
<asp:View ID="ViewCompany" runat="server">
<h2 class="form-section-title">Información de la empresa</h2>
<p class="form-section-help">Verificaremos que el RFC no se encuentre registrado antes de crear la cuenta.</p>
<div class="row"><div class="col-md-6 field"><label class="required">Razón social</label><asp:TextBox ID="TxtBusinessName" runat="server" CssClass="form-control" MaxLength="200" /></div><div class="col-md-6 field"><label>Nombre comercial</label><asp:TextBox ID="TxtTradeName" runat="server" CssClass="form-control" MaxLength="200" /></div></div>
<div class="row">
<div class="col-md-6 field"><label class="required">RFC</label><asp:TextBox ID="TxtRfc" runat="server" CssClass="form-control text-uppercase" MaxLength="15" /></div>
<div class="col-md-6 field"><label>Teléfono</label><asp:TextBox ID="TxtPhone" runat="server" CssClass="form-control" MaxLength="25" /></div>
</div>
<div class="row">
<div class="col-md-6 field">
<label class="required">Código postal</label>
<div class="postal-code-control">
<asp:TextBox ID="TxtPostalCode" runat="server" ClientIDMode="Static" CssClass="form-control" MaxLength="5" inputmode="numeric" autocomplete="postal-code" />
<asp:LinkButton ID="BtnSearchPostalCode" runat="server" ClientIDMode="Static" CssClass="postal-refresh" OnClick="BtnSearchPostalCode_Click" CausesValidation="false" ToolTip="Actualizar datos del código postal" aria-label="Actualizar datos del código postal"><i class="fa fa-refresh" aria-hidden="true"></i></asp:LinkButton>
</div>
<asp:Label ID="LblPostalStatus" runat="server" CssClass="postal-status" Visible="false" />
</div>
</div>
<div class="row">
<div class="col-md-4 field"><label>País</label><asp:TextBox ID="TxtCountry" runat="server" CssClass="form-control" MaxLength="50" Text="México" ReadOnly="true" /></div>
<div class="col-md-4 field"><label class="required">Estado</label><asp:DropDownList ID="DdlState" runat="server" CssClass="form-control" Enabled="false" /></div>
<div class="col-md-4 field"><label class="required">Ciudad o localidad</label><asp:TextBox ID="TxtCity" runat="server" CssClass="form-control" MaxLength="50" ReadOnly="true" /></div>
</div>
<div class="row">
<div class="col-md-12 field"><label class="required">Dirección</label><asp:TextBox ID="TxtAddress" runat="server" CssClass="form-control" MaxLength="90" /></div>
</div>
<div class="row">
<div class="col-md-3 field"><label>No. exterior</label><asp:TextBox ID="TxtExterior" runat="server" CssClass="form-control" MaxLength="20" /></div>
<div class="col-md-3 field"><label>No. interior</label><asp:TextBox ID="TxtInterior" runat="server" CssClass="form-control" MaxLength="50" /></div>
<div class="col-md-6 field"><label class="required">Colonia</label><asp:DropDownList ID="DdlNeighborhood" runat="server" CssClass="form-control" /></div>
</div>
<asp:HiddenField ID="HfCvePais" runat="server" />
<asp:HiddenField ID="HfCveMunicipio" runat="server" />
<asp:HiddenField ID="HfCveLocalidad" runat="server" />
<asp:HiddenField ID="HfResolvedPostalCode" runat="server" />
<asp:Panel ID="PnlCaptchaSection" runat="server" CssClass="captcha-section">
<span class="captcha-title">Validación de seguridad</span>
<asp:Panel ID="PnlCaptcha" runat="server" ClientIDMode="Static" CssClass="g-recaptcha" />
<asp:Label ID="LblCaptchaConfiguration" runat="server" CssClass="captcha-configuration" Text="La validación No soy un robot todavía no está configurada. Contacta a Sistemas." Visible="false" />
</asp:Panel>
<div class="actions"><asp:Button ID="BtnBack" runat="server" CssClass="btn back-link" Text="Regresar" OnClick="BtnBack_Click" CausesValidation="false" /><asp:Button ID="BtnRegister" runat="server" CssClass="btn green" Text="Crear cuenta" OnClick="BtnRegister_Click" /></div>
</asp:View>
</asp:MultiView>
</ContentTemplate>
</asp:UpdatePanel>
</main>
</div>
<div class="registration-security"><i class="fa fa-shield"></i> Tus datos se transmiten de forma segura y no se guardan hasta finalizar el registro.</div>
</div>
</form>
</body></html>
<script>
var sigPostalCodeTimer = null;
var sigRecaptchaWidgetId = null;
function sigSolicitarCodigoPostal() {
var input = document.getElementById('TxtPostalCode');
var button = document.getElementById('BtnSearchPostalCode');
if (!input || !button) return;
if (!/^\d{5}$/.test(input.value)) {
input.setCustomValidity('El código postal debe contener exactamente 5 dígitos.');
input.reportValidity();
return;
}
input.setCustomValidity('');
button.click();
}
function sigInicializarCodigoPostal() {
var input = document.getElementById('TxtPostalCode');
var button = document.getElementById('BtnSearchPostalCode');
if (!input || !button || input.getAttribute('data-sig-cp-inicializado') === '1') return;
input.setAttribute('data-sig-cp-inicializado', '1');
input.addEventListener('input', function () {
this.value = this.value.replace(/\D/g, '').substring(0, 5);
this.setCustomValidity('');
window.clearTimeout(sigPostalCodeTimer);
if (this.value.length === 5)
sigPostalCodeTimer = window.setTimeout(sigSolicitarCodigoPostal, 2000);
});
input.addEventListener('keydown', function (event) {
if (event.key !== 'Enter' && event.keyCode !== 13) return;
event.preventDefault();
event.stopPropagation();
window.clearTimeout(sigPostalCodeTimer);
sigSolicitarCodigoPostal();
});
button.addEventListener('click', function () {
window.clearTimeout(sigPostalCodeTimer);
var icon = this.querySelector('.fa-refresh');
if (icon) icon.classList.add('fa-spin');
});
}
function sigRenderRecaptcha() {
var container = document.getElementById('PnlCaptcha');
if (!container || !window.grecaptcha || container.getAttribute('data-rendered') === '1') return;
var siteKey = container.getAttribute('data-sitekey');
if (!siteKey) return;
sigRecaptchaWidgetId = window.grecaptcha.render(container, { sitekey: siteKey, theme: 'light' });
container.setAttribute('data-rendered', '1');
}
function sigRecaptchaLoaded() { sigRenderRecaptcha(); }
(function () {
function initializePageControls() {
sigInicializarCodigoPostal();
sigRenderRecaptcha();
}
if (window.Sys && Sys.WebForms) {
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(function () {
sigRecaptchaWidgetId = null;
initializePageControls();
});
}
if (document.readyState === 'loading')
document.addEventListener('DOMContentLoaded', initializePageControls);
else
initializePageControls();
}());
</script>
<script src="https://www.google.com/recaptcha/api.js?onload=sigRecaptchaLoaded&amp;render=explicit&amp;hl=es" async defer></script>
</body>
</html>

View File

@@ -1,48 +1,314 @@
using System;
using System.Text.RegularExpressions;
using System.Web.UI;
using System.Web.UI.WebControls;
using SIG.Web.AppCode.Configuration;
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Models.Security;
using SIG.Web.AppCode.Services.Database;
namespace SIG.Web.Pages.Security
{
public partial class RegistroCliente : Page
{
private const string DraftKey = "RegistrationDraft";
private PublicRegistrationDraft Draft { get { return ViewState[DraftKey] as PublicRegistrationDraft; } set { ViewState[DraftKey] = value; } }
private PublicRegistrationDraft Draft
{
get { return ViewState[DraftKey] as PublicRegistrationDraft; }
set { ViewState[DraftKey] = value; }
}
private string DomainName { get { return Request.Url.Host; } }
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) LoadStates(); }
protected void Page_Load(object sender, EventArgs e)
{
PnlCaptchaSection.Visible = AppServices.CaptchaService.IsEnabled;
PnlCaptcha.Attributes["data-sitekey"] = AppServices.CaptchaService.SiteKey;
PnlCaptcha.Visible = AppServices.CaptchaService.IsEnabled && AppServices.CaptchaService.IsConfigured;
LblCaptchaConfiguration.Visible = AppServices.CaptchaService.IsEnabled && !AppServices.CaptchaService.IsConfigured;
if (!IsPostBack)
{
LoadStates();
InitializeNeighborhoods();
}
}
protected void BtnNext_Click(object sender, EventArgs e)
{
HideMessage();
if (string.IsNullOrWhiteSpace(TxtUsername.Text) || string.IsNullOrWhiteSpace(TxtEmail.Text) || TxtPassword.Text.Length < 8) { ShowMessage("Captura usuario, correo y una contraseña de al menos 8 caracteres."); return; }
if (!string.Equals(TxtPassword.Text, TxtConfirmPassword.Text, StringComparison.Ordinal)) { ShowMessage("Las contraseñas no coinciden."); return; }
Draft = new PublicRegistrationDraft { NombreUsuario = TxtUsername.Text.Trim(), CorreoElectronico = TxtEmail.Text.Trim(), Contrasena = TxtPassword.Text };
MvRegistration.ActiveViewIndex = 1; SetStep(1);
if (string.IsNullOrWhiteSpace(TxtUsername.Text)
|| string.IsNullOrWhiteSpace(TxtEmail.Text)
|| TxtPassword.Text.Length < 8)
{
ShowMessage("Captura usuario, correo y una contraseña de al menos 8 caracteres.");
return;
}
if (!string.Equals(TxtPassword.Text, TxtConfirmPassword.Text, StringComparison.Ordinal))
{
ShowMessage("Las contraseñas no coinciden.");
return;
}
Draft = new PublicRegistrationDraft
{
NombreUsuario = TxtUsername.Text.Trim(),
CorreoElectronico = TxtEmail.Text.Trim(),
Contrasena = TxtPassword.Text
};
MvRegistration.ActiveViewIndex = 1;
SetStep(1);
}
protected void BtnBack_Click(object sender, EventArgs e) { MvRegistration.ActiveViewIndex = 0; SetStep(0); }
protected void BtnBack_Click(object sender, EventArgs e)
{
MvRegistration.ActiveViewIndex = 0;
SetStep(0);
}
protected void BtnSearchPostalCode_Click(object sender, EventArgs e)
{
HideMessage();
string selectedNeighborhoodValue = DdlNeighborhood.SelectedValue;
string selectedNeighborhoodText = DdlNeighborhood.SelectedItem == null
? string.Empty
: DdlNeighborhood.SelectedItem.Text.Trim();
string postalCode = (TxtPostalCode.Text ?? string.Empty).Trim();
if (!Regex.IsMatch(postalCode, @"^\d{5}$"))
{
ClearPostalCodeResult();
ShowMessage("El código postal debe contener exactamente 5 dígitos.");
return;
}
DbResult<PostalCodeLookupModel> result =
AppServices.PublicRegistrationService.GetPostalCode(postalCode, DomainName);
if (!result.Success)
{
StoredProcedureException procedureException = result.Exception as StoredProcedureException;
if (procedureException != null && procedureException.ErrorType == 2)
ClearPostalCodeResult();
ShowMessage(result.UserMessage);
return;
}
if (result.Data == null)
{
ClearPostalCodeResult();
ShowMessage("El código postal no fue localizado.");
return;
}
PostalCodeLookupModel data = result.Data;
ListItem stateItem = DdlState.Items.FindByValue(data.CveEstado.ToString());
if (stateItem == null)
{
ClearPostalCodeResult();
ShowMessage("El estado relacionado con el código postal no está disponible.");
return;
}
ClearPostalCodeResult();
DdlState.ClearSelection();
stateItem.Selected = true;
TxtCity.Text = data.Ciudad;
TxtCountry.Text = data.Pais;
HfCvePais.Value = data.CvePais.ToString();
HfCveMunicipio.Value = data.CveMunicipio.ToString();
HfCveLocalidad.Value = data.CveLocalidad.ToString();
HfResolvedPostalCode.Value = postalCode;
DdlNeighborhood.DataSource = data.Colonias;
DdlNeighborhood.DataTextField = "NombreAsentamiento";
DdlNeighborhood.DataValueField = "CveColonia";
DdlNeighborhood.DataBind();
DdlNeighborhood.Items.Insert(0, new ListItem("Selecciona una colonia...", "0"));
RestoreNeighborhoodSelection(selectedNeighborhoodValue, selectedNeighborhoodText);
if (data.Colonias == null || data.Colonias.Count == 0)
{
ShowMessage("El código postal fue localizado, pero no tiene colonias vigentes asociadas.");
return;
}
LblPostalStatus.Text = Server.HtmlEncode(
string.Format("{0}, {1}. Se encontraron {2} colonias.", data.Ciudad, data.Estado, data.Colonias.Count));
LblPostalStatus.CssClass = "postal-status success";
LblPostalStatus.Visible = true;
}
private void RestoreNeighborhoodSelection(string previousValue, string previousText)
{
ListItem matchingItem = null;
if (!string.IsNullOrWhiteSpace(previousValue) && previousValue != "0")
{
matchingItem = DdlNeighborhood.Items.FindByValue(previousValue);
}
if (matchingItem == null && !string.IsNullOrWhiteSpace(previousText))
{
foreach (ListItem item in DdlNeighborhood.Items)
{
if (item.Value != "0"
&& string.Equals(item.Text.Trim(), previousText, StringComparison.CurrentCultureIgnoreCase))
{
matchingItem = item;
break;
}
}
}
if (matchingItem == null) return;
DdlNeighborhood.ClearSelection();
matchingItem.Selected = true;
}
protected void BtnRegister_Click(object sender, EventArgs e)
{
HideMessage(); PublicRegistrationDraft draft = Draft; if (draft == null) { ShowMessage("La información de usuario expiró. Vuelve al primer paso."); return; }
draft.RazonSocial = TxtBusinessName.Text.Trim(); draft.NombreComercial = TxtTradeName.Text.Trim(); draft.RFC = TxtRfc.Text.Trim().ToUpperInvariant(); draft.Direccion = TxtAddress.Text.Trim(); draft.NoExt = TxtExterior.Text.Trim(); draft.NoInt = TxtInterior.Text.Trim(); draft.Colonia = TxtNeighborhood.Text.Trim(); draft.Ciudad = TxtCity.Text.Trim();
int state; int.TryParse(DdlState.SelectedValue, out state); draft.CveSATEstado = state; draft.Pais = TxtCountry.Text.Trim(); draft.CP = TxtPostalCode.Text.Trim(); draft.Telefono = TxtPhone.Text.Trim(); Draft = draft;
string verificationUrl = new Uri(Request.Url, ResolveUrl("~/Pages/Security/VerificarCorreo.aspx")).AbsoluteUri;
DbResult<bool> result = AppServices.PublicRegistrationService.Register(draft, verificationUrl, DomainName);
if (!result.Success) { ShowMessage(result.UserMessage); return; }
Draft = null; Response.Redirect(ResolveUrl("~/Pages/Security/RegistroPendiente.aspx"), false); Context.ApplicationInstance.CompleteRequest();
HideMessage();
PublicRegistrationDraft draft = Draft;
if (draft == null)
{
ShowMessage("La información de usuario expiró. Vuelve al primer paso.");
return;
}
string postalCode = (TxtPostalCode.Text ?? string.Empty).Trim();
if (!Regex.IsMatch(postalCode, @"^\d{5}$")
|| !string.Equals(postalCode, HfResolvedPostalCode.Value, StringComparison.Ordinal))
{
ShowMessage("Consulta o actualiza el código postal antes de crear la cuenta.");
return;
}
int cveColonia;
int cvePais;
int cveMunicipio;
int cveLocalidad;
int cveEstado;
int.TryParse(DdlNeighborhood.SelectedValue, out cveColonia);
int.TryParse(HfCvePais.Value, out cvePais);
int.TryParse(HfCveMunicipio.Value, out cveMunicipio);
int.TryParse(HfCveLocalidad.Value, out cveLocalidad);
int.TryParse(DdlState.SelectedValue, out cveEstado);
if (cveColonia <= 0 || cvePais <= 0 || cveMunicipio <= 0 || cveEstado <= 0)
{
ShowMessage("Selecciona una colonia válida para el código postal consultado.");
return;
}
draft.RazonSocial = TxtBusinessName.Text.Trim();
draft.NombreComercial = TxtTradeName.Text.Trim();
draft.RFC = TxtRfc.Text.Trim().ToUpperInvariant();
draft.Direccion = TxtAddress.Text.Trim();
draft.NoExt = TxtExterior.Text.Trim();
draft.NoInt = TxtInterior.Text.Trim();
draft.Colonia = DdlNeighborhood.SelectedItem.Text;
draft.Ciudad = TxtCity.Text.Trim();
draft.CveSATEstado = cveEstado;
draft.Pais = TxtCountry.Text.Trim();
draft.CP = postalCode;
draft.Telefono = TxtPhone.Text.Trim();
draft.CvePais = cvePais;
draft.CveMunicipio = cveMunicipio;
draft.CveLocalidad = cveLocalidad;
draft.CveColonia = cveColonia;
Draft = draft;
DbResult<bool> captcha = AppServices.CaptchaService.Verify(
Request.Form["g-recaptcha-response"],
Request.UserHostAddress,
Request.Url.Host);
if (!captcha.Success)
{
ShowMessage(captcha.UserMessage);
return;
}
string verificationUrl =
new Uri(Request.Url, ResolveUrl("~/Pages/Security/VerificarCorreo.aspx")).AbsoluteUri;
DbResult<bool> result =
AppServices.PublicRegistrationService.Register(draft, verificationUrl, DomainName);
if (!result.Success)
{
ShowMessage(result.UserMessage);
return;
}
Draft = null;
Response.Redirect(ResolveUrl("~/Pages/Security/RegistroPendiente.aspx"), false);
Context.ApplicationInstance.CompleteRequest();
}
private void LoadStates()
{
var result = AppServices.PublicRegistrationService.ListStates(DomainName); if (!result.Success) { ShowMessage(result.UserMessage); return; }
DdlState.DataSource = result.Data; DdlState.DataTextField = "NombreEstado"; DdlState.DataValueField = "CveEstado"; DdlState.DataBind(); DdlState.Items.Insert(0, new System.Web.UI.WebControls.ListItem("Selecciona...", "0"));
DbResult<System.Collections.Generic.List<StateOptionModel>> result =
AppServices.PublicRegistrationService.ListStates(DomainName);
if (!result.Success)
{
ShowMessage(result.UserMessage);
return;
}
DdlState.DataSource = result.Data;
DdlState.DataTextField = "NombreEstado";
DdlState.DataValueField = "CveEstado";
DdlState.DataBind();
DdlState.Items.Insert(0, new ListItem("Selecciona...", "0"));
}
private void InitializeNeighborhoods()
{
DdlNeighborhood.Items.Clear();
DdlNeighborhood.Items.Add(new ListItem("Captura y consulta el código postal...", "0"));
}
private void ClearPostalCodeResult()
{
HfCvePais.Value = string.Empty;
HfCveMunicipio.Value = string.Empty;
HfCveLocalidad.Value = string.Empty;
HfResolvedPostalCode.Value = string.Empty;
TxtCity.Text = string.Empty;
TxtCountry.Text = "México";
DdlState.ClearSelection();
if (DdlState.Items.Count > 0) DdlState.SelectedIndex = 0;
InitializeNeighborhoods();
LblPostalStatus.Visible = false;
}
private void SetStep(int index)
{
StepUser.Attributes["class"] = index == 0 ? "step active" : "step";
StepCompany.Attributes["class"] = index == 1 ? "step active" : "step";
}
private void ShowMessage(string message)
{
LblMessage.Text = Server.HtmlEncode(message);
LblMessage.Visible = true;
}
private void HideMessage()
{
LblMessage.Visible = false;
}
private void SetStep(int index) { StepUser.Attributes["class"] = index == 0 ? "step active" : "step"; StepCompany.Attributes["class"] = index == 1 ? "step active" : "step"; }
private void ShowMessage(string message) { LblMessage.Text = Server.HtmlEncode(message); LblMessage.Visible = true; }
private void HideMessage() { LblMessage.Visible = false; }
}
}

View File

@@ -22,12 +22,21 @@ namespace SIG.Web.Pages.Security
protected global::System.Web.UI.WebControls.TextBox TxtAddress;
protected global::System.Web.UI.WebControls.TextBox TxtExterior;
protected global::System.Web.UI.WebControls.TextBox TxtInterior;
protected global::System.Web.UI.WebControls.TextBox TxtNeighborhood;
protected global::System.Web.UI.WebControls.DropDownList DdlNeighborhood;
protected global::System.Web.UI.WebControls.TextBox TxtCity;
protected global::System.Web.UI.WebControls.DropDownList DdlState;
protected global::System.Web.UI.WebControls.TextBox TxtCountry;
protected global::System.Web.UI.WebControls.TextBox TxtPostalCode;
protected global::System.Web.UI.WebControls.LinkButton BtnSearchPostalCode;
protected global::System.Web.UI.WebControls.Label LblPostalStatus;
protected global::System.Web.UI.WebControls.HiddenField HfCvePais;
protected global::System.Web.UI.WebControls.HiddenField HfCveMunicipio;
protected global::System.Web.UI.WebControls.HiddenField HfCveLocalidad;
protected global::System.Web.UI.WebControls.HiddenField HfResolvedPostalCode;
protected global::System.Web.UI.WebControls.TextBox TxtPhone;
protected global::System.Web.UI.WebControls.Panel PnlCaptchaSection;
protected global::System.Web.UI.WebControls.Panel PnlCaptcha;
protected global::System.Web.UI.WebControls.Label LblCaptchaConfiguration;
protected global::System.Web.UI.WebControls.Button BtnBack;
protected global::System.Web.UI.WebControls.Button BtnRegister;
}

View File

@@ -77,6 +77,7 @@
<Compile Include="AppCode\Contracts\Security\ILoginAttemptRepository.cs" />
<Compile Include="AppCode\Contracts\Security\ILoginService.cs" />
<Compile Include="AppCode\Contracts\Security\IEmailSender.cs" />
<Compile Include="AppCode\Contracts\Security\ICaptchaService.cs" />
<Compile Include="AppCode\Contracts\Security\IPublicRegistrationService.cs" />
<Compile Include="AppCode\Contracts\Security\IUserAdministrationRepository.cs" />
<Compile Include="AppCode\Contracts\Security\IPasswordHasher.cs" />
@@ -119,6 +120,7 @@
<Compile Include="AppCode\Services\Security\LoginService.cs" />
<Compile Include="AppCode\Services\Security\PasswordHasher.cs" />
<Compile Include="AppCode\Services\Security\PublicRegistrationService.cs" />
<Compile Include="AppCode\Services\Security\RecaptchaV2Service.cs" />
<Compile Include="AppCode\Services\Security\SmtpEmailSender.cs" />
<Compile Include="AppCode\Services\Security\PermissionService.cs" />
<Compile Include="AppCode\Services\Security\WebFormsUserContextProvider.cs" />

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="domainConnections" type="System.Configuration.NameValueSectionHandler" />
@@ -21,10 +21,15 @@
<add key="SmtpUser" value="" />
<add key="SmtpPassword" value="" />
<add key="EmailFrom" value="" />
<!-- reCAPTCHA v2: al desactivarlo no se muestra ni se valida. La clave secreta nunca se envía al navegador. -->
<add key="RecaptchaEnabled" value="true" />
<add key="RecaptchaSiteKey" value="6LeVOFQtAAAAAOxZKnBiKFlnMnd90aR1PwJmUGgJ" />
<add key="RecaptchaSecretKey" value="6LeVOFQtAAAAAMGFabwoBAk6VG6b16d46hahBSwY" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.8.1" />
<httpRuntime targetFramework="4.8.1" maxRequestLength="102400" executionTimeout="600" />
<globalization requestEncoding="utf-8" responseEncoding="utf-8" fileEncoding="utf-8" culture="es-MX" uiCulture="es-MX" />
<pages controlRenderingCompatibilityVersion="4.0" clientIDMode="AutoID">
<namespaces>
<add namespace="System.Web.Optimization" />

View File

@@ -1,4 +1,4 @@
SET ANSI_NULLS ON;
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
@@ -220,6 +220,38 @@ BEGIN
END;
GO
/* Claves normalizadas utilizadas por el registro. */
IF OBJECT_ID(N'dbo.Clientes', N'U') IS NULL
THROW 51026, N'Falta la tabla dbo.Clientes.', 1;
IF COL_LENGTH(N'dbo.Clientes', N'CveLocalidad') IS NULL
ALTER TABLE [dbo].[Clientes] ADD [CveLocalidad] int NULL;
GO
IF OBJECT_ID(N'dbo.FK_Clientes_Gral_SAT_Pais', N'F') IS NULL
AND NOT EXISTS (SELECT 1 FROM [dbo].[Clientes] C LEFT JOIN [dbo].[Gral_SAT_Pais] P ON P.[CvePais]=C.[CvePais] WHERE C.[CvePais] IS NOT NULL AND P.[CvePais] IS NULL)
ALTER TABLE [dbo].[Clientes] WITH CHECK ADD CONSTRAINT [FK_Clientes_Gral_SAT_Pais] FOREIGN KEY ([CvePais]) REFERENCES [dbo].[Gral_SAT_Pais] ([CvePais]);
GO
IF OBJECT_ID(N'dbo.FK_Clientes_Gral_SAT_Estado_Normalizado', N'F') IS NULL
AND NOT EXISTS (SELECT 1 FROM [dbo].[Clientes] C LEFT JOIN [dbo].[Gral_SAT_Estado] E ON E.[CveEstado]=C.[CveEstado] WHERE C.[CveEstado] IS NOT NULL AND E.[CveEstado] IS NULL)
ALTER TABLE [dbo].[Clientes] WITH CHECK ADD CONSTRAINT [FK_Clientes_Gral_SAT_Estado_Normalizado] FOREIGN KEY ([CveEstado]) REFERENCES [dbo].[Gral_SAT_Estado] ([CveEstado]);
GO
IF OBJECT_ID(N'dbo.FK_Clientes_Gral_SAT_Municipio', N'F') IS NULL
AND NOT EXISTS (SELECT 1 FROM [dbo].[Clientes] C LEFT JOIN [dbo].[Gral_SAT_Municipio] M ON M.[CveMunicipio]=C.[CveMunicipio] WHERE C.[CveMunicipio] IS NOT NULL AND M.[CveMunicipio] IS NULL)
ALTER TABLE [dbo].[Clientes] WITH CHECK ADD CONSTRAINT [FK_Clientes_Gral_SAT_Municipio] FOREIGN KEY ([CveMunicipio]) REFERENCES [dbo].[Gral_SAT_Municipio] ([CveMunicipio]);
GO
IF OBJECT_ID(N'dbo.FK_Clientes_Gral_SAT_Localidad', N'F') IS NULL
ALTER TABLE [dbo].[Clientes] WITH CHECK ADD CONSTRAINT [FK_Clientes_Gral_SAT_Localidad] FOREIGN KEY ([CveLocalidad]) REFERENCES [dbo].[Gral_SAT_Localidad] ([CveLocalidad]);
GO
IF OBJECT_ID(N'dbo.FK_Clientes_Gral_SAT_Colonia', N'F') IS NULL
AND NOT EXISTS (SELECT 1 FROM [dbo].[Clientes] C LEFT JOIN [dbo].[Gral_SAT_Colonia] CO ON CO.[CveColonia]=C.[CveColonia] WHERE C.[CveColonia] IS NOT NULL AND CO.[CveColonia] IS NULL)
ALTER TABLE [dbo].[Clientes] WITH CHECK ADD CONSTRAINT [FK_Clientes_Gral_SAT_Colonia] FOREIGN KEY ([CveColonia]) REFERENCES [dbo].[Gral_SAT_Colonia] ([CveColonia]);
GO
CREATE OR ALTER PROCEDURE [dbo].[PA_Sis_RegistroClienteAdd]
@NombreUsuario varchar(100),
@HashContrasena varchar(500),
@@ -230,12 +262,16 @@ CREATE OR ALTER PROCEDURE [dbo].[PA_Sis_RegistroClienteAdd]
@Direccion varchar(90),
@NoExt varchar(20) = NULL,
@NoInt varchar(50) = NULL,
@Colonia varchar(50),
@Ciudad varchar(50),
@Colonia varchar(100),
@Ciudad varchar(100),
@CveSATEstado int,
@Pais varchar(50) = 'México',
@CP varchar(10) = NULL,
@Pais varchar(100) = 'México',
@CP varchar(5),
@Telefono varchar(25) = NULL,
@CvePais int,
@CveMunicipio int,
@CveLocalidad int = NULL,
@CveColonia int,
@HashToken char(64),
@HorasExpiracion int = 24,
@CveUsuarioEjecuta int,
@@ -247,72 +283,96 @@ BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
SELECT @ErrorMostrar = NULL, @ErrorTipo = 0, @Return = 0;
BEGIN TRY
DECLARE @CveRolEmpresa int;
DECLARE @CveUsuario int;
DECLARE @CveCliente int;
DECLARE @NombreEstado varchar(35);
DECLARE @CveRolEmpresa int, @CveUsuario int, @CveCliente int;
DECLARE @NombreEstado varchar(50), @PaisCatalogo varchar(100);
DECLARE @CiudadCatalogo varchar(100), @ColoniaCatalogo varchar(100);
SET @NombreUsuario = LTRIM(RTRIM(@NombreUsuario));
SET @CorreoElectronico = LOWER(LTRIM(RTRIM(@CorreoElectronico)));
SET @RFC = UPPER(REPLACE(LTRIM(RTRIM(@RFC)), ' ', ''));
SET @CP = LTRIM(RTRIM(@CP));
SELECT @CveRolEmpresa = [CveRol]
FROM [dbo].[Sis_Roles]
WHERE [NombreRol] = 'Empresa' AND [Activo] = (1) AND [Borrado] = (0);
SELECT @NombreEstado = LEFT([NombreEstado], 35)
FROM [dbo].[Gral_SAT_Estado]
WHERE [CveEstado] = @CveSATEstado AND [Borrado] = (0);
SELECT TOP (1)
@NombreEstado = LEFT(E.[NombreEstado], 35),
@PaisCatalogo = LEFT(P.[Descripcion], 50),
@CiudadCatalogo = LEFT(COALESCE(NULLIF(L.[NombreLocalidad], ''), M.[Descripcion]), 50),
@ColoniaCatalogo = LEFT(C.[NombreAsentamiento], 50)
FROM [dbo].[Gral_SAT_CP] AS CP
INNER JOIN [dbo].[Gral_SAT_Estado] AS E
ON E.[ClaveSATEstado] = CP.[ClaveSATEstado]
AND E.[CveEstado] = @CveSATEstado AND E.[Borrado] = (0)
INNER JOIN [dbo].[Gral_SAT_Pais] AS P
ON P.[ClaveSATPais] = E.[ClaveSATPais]
AND P.[CvePais] = @CvePais AND P.[Borrado] = (0)
INNER JOIN [dbo].[Gral_SAT_Municipio] AS M
ON M.[ClaveSATEstado] = CP.[ClaveSATEstado]
AND M.[ClaveSATMunicipio] = CP.[ClaveSATMunicipio]
AND M.[CveMunicipio] = @CveMunicipio AND M.[Borrado] = (0)
LEFT JOIN [dbo].[Gral_SAT_Localidad] AS L
ON L.[ClaveSATEstado] = CP.[ClaveSATEstado]
AND L.[ClaveSATLocalidad] = CP.[ClaveSATLocalidad] AND L.[Borrado] = (0)
INNER JOIN [dbo].[Gral_SAT_Colonia] AS C
ON C.[CP] = CP.[CP] AND C.[CveColonia] = @CveColonia AND C.[Borrado] = (0)
WHERE CP.[CP] = @CP AND CP.[Borrado] = (0)
AND (@CveLocalidad IS NULL OR L.[CveLocalidad] = @CveLocalidad);
IF @CveRolEmpresa IS NULL OR @NombreEstado IS NULL
IF @CveRolEmpresa IS NULL
BEGIN
SET @ErrorMostrar = 'No fue posible determinar el rol Empresa o el estado seleccionado.';
SET @ErrorTipo = 1;
SELECT @ErrorMostrar = 'No fue posible determinar el rol Empresa.', @ErrorTipo = 1;
RETURN;
END;
IF @NombreEstado IS NULL OR @PaisCatalogo IS NULL OR @CiudadCatalogo IS NULL OR @ColoniaCatalogo IS NULL
BEGIN
SELECT @ErrorMostrar = 'Los datos del código postal no coinciden con los catálogos vigentes. Actualiza el código postal.', @ErrorTipo = 2;
RETURN;
END;
SELECT @Pais = @PaisCatalogo, @Ciudad = @CiudadCatalogo, @Colonia = @ColoniaCatalogo;
IF EXISTS (SELECT 1 FROM [dbo].[Sis_Usuarios] WHERE [NombreUsuario] = @NombreUsuario)
OR EXISTS (SELECT 1 FROM [dbo].[Sis_Usuarios] WHERE LOWER([CorreoElectronico]) = @CorreoElectronico)
OR EXISTS (SELECT 1 FROM [dbo].[Clientes] WHERE UPPER(REPLACE(LTRIM(RTRIM([RFC])), ' ', '')) = @RFC)
BEGIN
SET @ErrorMostrar = 'Los datos ya fueron registrados por otra solicitud. Actualiza la página e inténtalo nuevamente.';
SET @ErrorTipo = 2;
SELECT @ErrorMostrar = 'Los datos ya fueron registrados por otra solicitud. Actualiza la página e inténtalo nuevamente.', @ErrorTipo = 2;
RETURN;
END;
BEGIN TRANSACTION;
INSERT INTO [dbo].[Sis_Usuarios]
([NombreUsuario], [HashContrasena], [CorreoElectronico], [CveRol], [Activo],
[DebeCambiarContrasena], [CorreoVerificado])
VALUES
(@NombreUsuario, @HashContrasena, @CorreoElectronico, @CveRolEmpresa, (0), (0), (0));
([NombreUsuario], [HashContrasena], [CorreoElectronico], [CveRol], [Activo], [DebeCambiarContrasena], [CorreoVerificado])
VALUES (@NombreUsuario, @HashContrasena, @CorreoElectronico, @CveRolEmpresa, (0), (0), (0));
SET @CveUsuario = CONVERT(int, SCOPE_IDENTITY());
INSERT INTO [dbo].[Clientes]
([Nombre], [NombreComercial], [Direccion], [NoExt], [NoInt], [Colonia],
[Ciudad], [Estado], [CveSATEstado], [Pais], [RFC], [CP], [Telefono],
[Mail], [Estatus], [Fecha], [CveUsuario], [FechaIng])
[Mail], [Estatus], [Fecha], [CveUsuario], [FechaIng],
[CvePais], [CveEstado], [CveMunicipio], [CveLocalidad], [CveColonia])
VALUES
(@RazonSocial, NULLIF(@NombreComercial, ''), @Direccion, NULLIF(@NoExt, ''),
NULLIF(@NoInt, ''), @Colonia, @Ciudad, @NombreEstado, @CveSATEstado,
COALESCE(NULLIF(@Pais, ''), 'México'), @RFC, NULLIF(@CP, ''),
NULLIF(@Telefono, ''), @CorreoElectronico, (0), CONVERT(date, GETDATE()),
@CveUsuario, GETDATE());
@Pais, @RFC, @CP, NULLIF(@Telefono, ''), @CorreoElectronico, (0),
CONVERT(date, GETDATE()), @CveUsuario, GETDATE(),
@CvePais, @CveSATEstado, @CveMunicipio, @CveLocalidad, @CveColonia);
SET @CveCliente = CONVERT(int, SCOPE_IDENTITY());
INSERT INTO [dbo].[Sis_UsuariosClientes]
([CveUsuario], [CveCliente], [Orden], [EsPrincipal], [Activo], [CveUsuarioRegistro])
VALUES
(@CveUsuario, @CveCliente, (1), (1), (1), @CveUsuario);
VALUES (@CveUsuario, @CveCliente, (1), (1), (1), @CveUsuario);
INSERT INTO [dbo].[Sis_UsuariosVerificacionesCorreo]
([CveUsuario], [CveClienteRegistro], [HashToken], [FechaExpiracion])
VALUES
(@CveUsuario, @CveCliente, @HashToken, DATEADD(HOUR, @HorasExpiracion, SYSDATETIME()));
VALUES (@CveUsuario, @CveCliente, @HashToken, DATEADD(HOUR, @HorasExpiracion, SYSDATETIME()));
COMMIT TRANSACTION;
@@ -321,9 +381,7 @@ BEGIN
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
SET @ErrorMostrar = 'Ocurrió un error al crear el registro; favor de contactar a Sistemas.';
SET @ErrorTipo = 1;
SET @Return = 0;
SELECT @ErrorMostrar = 'Ocurrió un error al crear el registro; favor de contactar a Sistemas.', @ErrorTipo = 1, @Return = 0;
DECLARE @E nvarchar(2048) = CONCAT('Número: ', ERROR_NUMBER(), '; Procedimiento: ', ERROR_PROCEDURE(), '; Línea: ', ERROR_LINE(), '; Detalle: ', ERROR_MESSAGE());
RAISERROR(N'%s', 10, 1, @E);
END CATCH;