Initial commit

This commit is contained in:
Jorge Luis caudillo esparza
2026-07-14 11:15:20 -07:00
commit 6685ef63fb
7326 changed files with 1020873 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using SIG.Web.AppCode.Contracts.Database;
namespace SIG.Web.AppCode.Services.Database
{
public sealed class DbConnectionFactory : IDbConnectionFactory
{
public IDbConnection CreateConnection(string domainName)
{
string name = ConfigurationManager.AppSettings["DefaultConnectionStringName"];
if (string.IsNullOrWhiteSpace(name)) name = "SIG_DefaultSql";
ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings[name];
if (settings == null) throw new InvalidOperationException("Connection string not found: " + name);
return new SqlConnection(settings.ConnectionString);
}
}
}

View File

@@ -0,0 +1,162 @@
using SIG.Web.AppCode.Contracts.Database;
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Services.Logging;
using System;
using System.Data;
using System.Data.SqlClient;
namespace SIG.Web.AppCode.Services.Database
{
public sealed class DbExecutor : IDbExecutor
{
private readonly IDbConnectionFactory _connectionFactory;
public DbExecutor(IDbConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}
public DbResult<T> Execute<T>(string domainName, Func<IDbConnection, T> action)
{
IDbConnection connection = null;
SqlConnection sqlConnection = null;
SqlInfoMessageEventHandler infoMessageHandler = null;
try
{
if (action == null)
{
throw new ArgumentNullException("action");
}
connection = _connectionFactory.CreateConnection(domainName);
sqlConnection = connection as SqlConnection;
if (sqlConnection != null)
{
/*
* No activar FireInfoMessageEventOnUserErrors: al hacerlo, SQL Server
* degrada errores de severidad 11-16 a InfoMessage y evita el catch.
* Los RAISERROR informativos de severidad 10 llegan al evento sin esa opción.
*/
infoMessageHandler = delegate(object sender, SqlInfoMessageEventArgs eventArgs)
{
foreach (SqlError error in eventArgs.Errors)
{
ExceptionLogger.Log(
CreateDiagnosticException(new InvalidOperationException(error.Message)),
"DbExecutor.SqlInfoMessage");
}
};
sqlConnection.InfoMessage += infoMessageHandler;
}
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
T data = action(connection);
return DbResult<T>.Ok(data);
}
catch (StoredProcedureException ex)
{
ExceptionLogger.Log(CreateDiagnosticException(ex), "DbExecutor.StoredProcedure", ex.UserMessage);
return DbResult<T>.Fail(ex.UserMessage, BuildTechnicalMessage(ex), ex);
}
catch (SqlException ex)
{
string userMessage;
switch (ex.Number)
{
case 208:
userMessage = "Falta una tabla, vista o procedimiento requerido en la base de datos.";
break;
case 207:
userMessage = "Falta una columna requerida en la base de datos.";
break;
case 2812:
userMessage = "Falta un procedimiento requerido en la base de datos.";
break;
case 4060:
case 18456:
userMessage = "No fue posible abrir la base de datos configurada.";
break;
case -2:
userMessage = "La base de datos tardó demasiado en responder.";
break;
default:
userMessage = "Ocurrió un error al consultar la base de datos.";
break;
}
ExceptionLogger.Log(CreateDiagnosticException(ex), "DbExecutor.SqlException", userMessage);
return DbResult<T>.Fail(userMessage, BuildTechnicalMessage(ex), ex);
}
catch (TimeoutException ex)
{
const string userMessage = "La base de datos tardó demasiado en responder. Intente nuevamente.";
ExceptionLogger.Log(CreateDiagnosticException(ex), "DbExecutor.TimeoutException", userMessage);
return DbResult<T>.Fail(userMessage, BuildTechnicalMessage(ex), ex);
}
catch (Exception ex)
{
const string userMessage = "Ocurrió un error al consultar la base de datos.";
ExceptionLogger.Log(CreateDiagnosticException(ex), "DbExecutor.Exception", userMessage);
return DbResult<T>.Fail(userMessage, BuildTechnicalMessage(ex), ex);
}
finally
{
if (sqlConnection != null && infoMessageHandler != null)
{
sqlConnection.InfoMessage -= infoMessageHandler;
}
if (connection != null)
{
try
{
if (connection.State != ConnectionState.Closed)
{
connection.Close();
}
}
finally
{
connection.Dispose();
}
}
StoredProcedureDiagnostics.Clear();
}
}
private static Exception CreateDiagnosticException(Exception exception)
{
string diagnostic = StoredProcedureDiagnostics.BuildDiagnostic();
if (string.IsNullOrWhiteSpace(diagnostic))
{
return exception;
}
return new InvalidOperationException(
exception.Message + Environment.NewLine + diagnostic,
exception);
}
private static string BuildTechnicalMessage(Exception exception)
{
string diagnostic = StoredProcedureDiagnostics.BuildDiagnostic();
return string.IsNullOrWhiteSpace(diagnostic)
? exception.Message
: exception.Message + Environment.NewLine + diagnostic;
}
}
}

View File

@@ -0,0 +1,133 @@
using System;
using System.Data;
using SIG.Web.AppCode.Models.Common;
namespace SIG.Web.AppCode.Services.Database
{
public static class StoredProcedureCommand
{
public static void Configure(IDbCommand command, string procedureName, int cveUsuarioEjecuta)
{
if (command == null)
{
throw new ArgumentNullException("command");
}
StoredProcedureDiagnostics.Start(procedureName);
command.CommandText = procedureName;
command.CommandType = CommandType.StoredProcedure;
AddInput(command, "@CveUsuarioEjecuta", cveUsuarioEjecuta, DbType.Int32);
AddOutput(command, "@ErrorMostrar", DbType.String, -1);
AddOutput(command, "@ErrorTipo", DbType.Int32, 0);
AddOutput(command, "@Return", DbType.Boolean, 0);
}
public static IDbDataParameter AddInput(IDbCommand command, string name, object value)
{
return AddInput(command, name, value, null);
}
public static IDbDataParameter AddInput(IDbCommand command, string name, object value, DbType? dbType)
{
IDbDataParameter parameter = command.CreateParameter();
parameter.ParameterName = name;
parameter.Direction = ParameterDirection.Input;
parameter.Value = value ?? DBNull.Value;
if (dbType.HasValue)
{
parameter.DbType = dbType.Value;
}
command.Parameters.Add(parameter);
StoredProcedureDiagnostics.AddParameter(
parameter.ParameterName,
parameter.Value,
parameter.DbType,
parameter.Direction);
return parameter;
}
public static StoredProcedureResult ReadResult(IDbCommand command)
{
StoredProcedureDiagnostics.CaptureOutputs(command);
return new StoredProcedureResult
{
Return = ReadBoolean(command, "@Return"),
ErrorTipo = ReadInteger(command, "@ErrorTipo"),
ErrorMostrar = ReadString(command, "@ErrorMostrar")
};
}
public static void EnsureSuccess(IDbCommand command)
{
StoredProcedureResult result = ReadResult(command);
if (result.Return)
{
return;
}
object returnValue = ((IDataParameter)command.Parameters["@Return"]).Value;
bool outputsReceived = returnValue != null && returnValue != DBNull.Value;
string message;
if (!string.IsNullOrWhiteSpace(result.ErrorMostrar))
{
message = result.ErrorMostrar;
}
else if (!outputsReceived)
{
message = "La versión instalada del procedimiento no devolvió los parámetros requeridos; favor de contactar a Sistemas.";
}
else
{
message = "La operación no pudo completarse.";
}
throw new StoredProcedureException(message, result.ErrorTipo);
}
private static void AddOutput(IDbCommand command, string name, DbType dbType, int size)
{
IDbDataParameter parameter = command.CreateParameter();
parameter.ParameterName = name;
parameter.Direction = ParameterDirection.Output;
parameter.DbType = dbType;
if (size != 0)
{
parameter.Size = size;
}
command.Parameters.Add(parameter);
StoredProcedureDiagnostics.AddParameter(
parameter.ParameterName,
parameter.Value,
parameter.DbType,
parameter.Direction);
}
private static bool ReadBoolean(IDbCommand command, string name)
{
object value = ((IDataParameter)command.Parameters[name]).Value;
return value != null && value != DBNull.Value && Convert.ToBoolean(value);
}
private static int ReadInteger(IDbCommand command, string name)
{
object value = ((IDataParameter)command.Parameters[name]).Value;
return value == null || value == DBNull.Value ? 0 : Convert.ToInt32(value);
}
private static string ReadString(IDbCommand command, string name)
{
object value = ((IDataParameter)command.Parameters[name]).Value;
return value == null || value == DBNull.Value ? string.Empty : Convert.ToString(value);
}
}
}

View File

@@ -0,0 +1,276 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
namespace SIG.Web.AppCode.Services.Database
{
public static class StoredProcedureDiagnostics
{
private static readonly AsyncLocal<DiagnosticContext> CurrentContext =
new AsyncLocal<DiagnosticContext>();
public static void Start(string procedureName)
{
CurrentContext.Value = new DiagnosticContext
{
ProcedureName = procedureName
};
}
public static void AddParameter(string name, object value, DbType dbType, ParameterDirection direction)
{
DiagnosticContext context = CurrentContext.Value;
if (context == null)
{
return;
}
context.Parameters.Add(new DiagnosticParameter
{
Name = name,
Value = value,
DbType = dbType,
Direction = direction
});
}
public static void CaptureOutputs(IDbCommand command)
{
DiagnosticContext context = CurrentContext.Value;
if (context == null || command == null)
{
return;
}
foreach (IDataParameter commandParameter in command.Parameters)
{
if (commandParameter.Direction == ParameterDirection.Input)
{
continue;
}
DiagnosticParameter parameter = context.Parameters.FirstOrDefault(
item => item.Name.Equals(commandParameter.ParameterName, StringComparison.OrdinalIgnoreCase));
if (parameter != null)
{
parameter.Value = commandParameter.Value;
}
}
}
public static string BuildDiagnostic()
{
DiagnosticContext context = CurrentContext.Value;
if (context == null || string.IsNullOrWhiteSpace(context.ProcedureName))
{
return string.Empty;
}
var result = new StringBuilder();
result.AppendLine("StoredProcedure: " + context.ProcedureName);
result.AppendLine("Parametros:");
foreach (DiagnosticParameter parameter in context.Parameters)
{
result.AppendLine(parameter.Name + "=" + FormatSqlValue(parameter));
}
result.AppendLine("ReplaySql:");
AppendReplaySql(result, context);
return result.ToString();
}
public static void Clear()
{
CurrentContext.Value = null;
}
private static void AppendReplaySql(StringBuilder result, DiagnosticContext context)
{
IList<DiagnosticParameter> outputs = context.Parameters
.Where(parameter => parameter.Direction != ParameterDirection.Input)
.ToList();
foreach (DiagnosticParameter output in outputs)
{
result.Append("DECLARE ")
.Append(output.Name)
.Append(' ')
.Append(GetSqlType(output.DbType))
.AppendLine(";");
}
result.Append("EXEC ").Append(QuoteProcedureName(context.ProcedureName));
for (int index = 0; index < context.Parameters.Count; index++)
{
DiagnosticParameter parameter = context.Parameters[index];
result.Append(index == 0 ? Environment.NewLine + " " : "," + Environment.NewLine + " ");
result.Append(parameter.Name).Append(" = ");
if (parameter.Direction == ParameterDirection.Input)
{
result.Append(FormatSqlValue(parameter));
}
else
{
result.Append(parameter.Name).Append(" OUTPUT");
}
}
result.AppendLine(";");
if (outputs.Count > 0)
{
result.Append("SELECT ");
for (int index = 0; index < outputs.Count; index++)
{
if (index > 0)
{
result.Append(", ");
}
result.Append(outputs[index].Name);
}
result.AppendLine(";");
}
}
private static string FormatSqlValue(DiagnosticParameter parameter)
{
if (IsSensitive(parameter.Name))
{
return "N'[REDACTADO]'";
}
object value = parameter.Value;
if (value == null || value == DBNull.Value)
{
return "NULL";
}
if (value is bool)
{
return (bool)value ? "1" : "0";
}
if (value is DateTime)
{
return "'" + ((DateTime)value).ToString("yyyy-MM-ddTHH:mm:ss.fff", CultureInfo.InvariantCulture) + "'";
}
if (value is DateTimeOffset)
{
return "'" + ((DateTimeOffset)value).ToString("o", CultureInfo.InvariantCulture) + "'";
}
if (value is Guid)
{
return "'" + value + "'";
}
if (value is byte[])
{
byte[] bytes = (byte[])value;
int length = Math.Min(bytes.Length, 64);
return "0x" + BitConverter.ToString(bytes, 0, length).Replace("-", string.Empty)
+ (bytes.Length > length ? "/*TRUNCADO*/" : string.Empty);
}
if (value is string || value is char)
{
string text = Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;
if (text.Length > 1000)
{
text = text.Substring(0, 1000) + " [TRUNCADO]";
}
return "N'" + text.Replace("'", "''") + "'";
}
if (value is float || value is double || value is decimal ||
value is byte || value is sbyte || value is short || value is ushort ||
value is int || value is uint || value is long || value is ulong)
{
return Convert.ToString(value, CultureInfo.InvariantCulture);
}
string fallback = Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;
return "N'" + fallback.Replace("'", "''") + "'";
}
private static bool IsSensitive(string parameterName)
{
string name = (parameterName ?? string.Empty).ToLowerInvariant();
return name.Contains("contrasena") ||
name.Contains("password") ||
name.Contains("hash") ||
name.Contains("token") ||
name.Contains("secreto") ||
name.Contains("secret") ||
name.Contains("apikey") ||
name.Contains("api_key");
}
private static string GetSqlType(DbType dbType)
{
switch (dbType)
{
case DbType.Boolean: return "bit";
case DbType.Byte: return "tinyint";
case DbType.Int16: return "smallint";
case DbType.Int32: return "int";
case DbType.Int64: return "bigint";
case DbType.Decimal: return "decimal(38, 10)";
case DbType.Double: return "float";
case DbType.Single: return "real";
case DbType.Date: return "date";
case DbType.DateTime:
case DbType.DateTime2: return "datetime2(3)";
case DbType.DateTimeOffset: return "datetimeoffset(7)";
case DbType.Guid: return "uniqueidentifier";
case DbType.Binary: return "varbinary(max)";
case DbType.AnsiString: return "varchar(max)";
default: return "nvarchar(max)";
}
}
private static string QuoteProcedureName(string procedureName)
{
return string.Join(".", (procedureName ?? string.Empty)
.Split('.')
.Select(part => "[" + part.Replace("]", "]]" ) + "]"));
}
private sealed class DiagnosticContext
{
public DiagnosticContext()
{
Parameters = new List<DiagnosticParameter>();
}
public string ProcedureName { get; set; }
public IList<DiagnosticParameter> Parameters { get; private set; }
}
private sealed class DiagnosticParameter
{
public string Name { get; set; }
public object Value { get; set; }
public DbType DbType { get; set; }
public ParameterDirection Direction { get; set; }
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace SIG.Web.AppCode.Services.Database
{
public sealed class StoredProcedureException : Exception
{
public StoredProcedureException(string userMessage, int errorType)
: base(userMessage)
{
UserMessage = userMessage;
ErrorType = errorType;
}
public string UserMessage { get; private set; }
public int ErrorType { get; private set; }
}
}