Initial commit
This commit is contained in:
37
.gitattributes
vendored
Normal file
37
.gitattributes
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# Texto del proyecto
|
||||
* text=auto
|
||||
*.cs text eol=crlf
|
||||
*.aspx text eol=crlf
|
||||
*.ascx text eol=crlf
|
||||
*.master text eol=crlf
|
||||
*.config text eol=crlf
|
||||
*.csproj text eol=crlf
|
||||
*.sln text eol=crlf
|
||||
*.sql text eol=crlf
|
||||
*.ps1 text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.bat text eol=crlf
|
||||
*.md text eol=lf
|
||||
*.js text eol=lf
|
||||
*.css text eol=lf
|
||||
*.json text eol=lf
|
||||
*.xml text eol=crlf
|
||||
|
||||
# Binarios: nunca normalizar saltos de línea
|
||||
*.pdf binary
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.bmp binary
|
||||
*.webp binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.otf binary
|
||||
*.zip binary
|
||||
*.dll binary
|
||||
*.pdb binary
|
||||
*.exe binary
|
||||
38
.github/copilot-instructions.md
vendored
Normal file
38
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
|
||||
# AGENTS.md
|
||||
|
||||
## Project
|
||||
This is an ASP.NET WebForms project in C# targeting .NET Framework 4.8.
|
||||
|
||||
## Core rules
|
||||
- Do not add VB.NET code.
|
||||
- Do not convert WebForms pages to ASP.NET Core.
|
||||
- Keep pages as .aspx, .ascx, .master, and C# code-behind.
|
||||
- Keep the copied `Styles`, `Scripts`, and `Metronic` folders unchanged unless the user explicitly asks for a cleanup.
|
||||
- Do not connect WebForms pages directly to database classes.
|
||||
- Pages must use contracts and services from `AppCode`.
|
||||
- Use `SIG` as the prefix for new controls/classes.
|
||||
- Do not use `SITIC` in new examples, namespaces, or file names.
|
||||
- `Site.master` must keep ContentPlaceHolder IDs short: `H`, `C`, `J`.
|
||||
- Do not restore the hidden searcher in the header.
|
||||
- Keep the header actions button.
|
||||
- User data must come from `IUserContextProvider`.
|
||||
- Menu data must come from `IMenuProvider`.
|
||||
- Login must use `ILoginService`.
|
||||
- Search controls must use `IBuscadorCatalogoDataProvider`.
|
||||
- `ClsPaginaBase` must only handle session and permissions.
|
||||
- Database access must go through `IDomainConnectionResolver` and `IDbConnectionFactory`.
|
||||
- Notification channels must go through the notification center contracts.
|
||||
|
||||
## Folder rules
|
||||
- Configuration files go under `AppCode/Configuration`.
|
||||
- Session/user/menu contracts go under `AppCode/Session`.
|
||||
- Permission-related code goes under `AppCode/Security`.
|
||||
- Business process folders go under `AppCode/Modules` later.
|
||||
- Do not create process folders such as Sales/Purchases/Inventory until the user asks.
|
||||
|
||||
## Coding style
|
||||
- Prefer small contracts and small services.
|
||||
- Avoid large static helper classes.
|
||||
- Avoid putting SQL in pages or controls.
|
||||
- Prefer explicit null checks and clear error messages.
|
||||
74
.gitignore
vendored
Normal file
74
.gitignore
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
# Visual Studio
|
||||
.vs/
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Compilación .NET Framework / ASP.NET
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
x64/
|
||||
x86/
|
||||
TestResults/
|
||||
artifacts/
|
||||
|
||||
# NuGet (se restauran desde el proyecto)
|
||||
packages/
|
||||
*.nupkg
|
||||
*.snupkg
|
||||
|
||||
# Publicación
|
||||
publish/
|
||||
PublishProfiles/
|
||||
*.Publish.xml
|
||||
*.pubxml.user
|
||||
|
||||
# Archivos locales de IIS / WebForms
|
||||
*.csproj.user
|
||||
*.sln.cache
|
||||
IISExpress/
|
||||
|
||||
# Logs y datos temporales de la aplicación
|
||||
**/Log/*
|
||||
**/log/*
|
||||
!**/Log/.gitkeep
|
||||
!**/log/.gitkeep
|
||||
SIG.Web/App_Data/*.mdf
|
||||
SIG.Web/App_Data/*.ldf
|
||||
SIG.Web/App_Data/*.log
|
||||
**/App_Data/log.txt
|
||||
SIG.Web/App_Data/Temp/
|
||||
SIG.Web/App_Data/Uploads/
|
||||
|
||||
# Herramientas y trabajo local de Codex
|
||||
.codex-work/
|
||||
.agents/
|
||||
.codex_patch_probe.txt
|
||||
|
||||
# Sistema operativo
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
.DS_Store
|
||||
|
||||
# Temporales y respaldos
|
||||
*.tmp
|
||||
*.temp
|
||||
*.bak
|
||||
*.orig
|
||||
*.rej
|
||||
*~
|
||||
|
||||
# Cobertura y pruebas
|
||||
coverage/
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# Secretos locales opcionales
|
||||
*.secrets.json
|
||||
Web.local.config
|
||||
appsettings.local.json
|
||||
768
AGENTS.md
Normal file
768
AGENTS.md
Normal file
@@ -0,0 +1,768 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Project identity
|
||||
|
||||
This project is **Plataforma de Desarrolladores Agua Hillo**.
|
||||
|
||||
It is an **ASP.NET WebForms** application using **C#** and **.NET Framework 4.8 or 4.8.1**.
|
||||
|
||||
Do **not** convert this project to:
|
||||
|
||||
- ASP.NET Core
|
||||
- Razor Pages
|
||||
- MVC Core
|
||||
- Blazor
|
||||
- Minimal APIs
|
||||
- Angular-only app
|
||||
- Web API-only app
|
||||
|
||||
ASP.NET WebForms must stay on .NET Framework. If a command, package, or template requires ASP.NET Core, do not use it for this project.
|
||||
|
||||
---
|
||||
|
||||
## Main goal
|
||||
|
||||
Create a clean WebForms starter project from zero, keeping the same Metronic visual style and only the necessary bundle methods used by the master page and login page.
|
||||
|
||||
The project must use a contract/service structure so pages, master page, login, menu, user session, and future modules do not connect directly to database classes.
|
||||
|
||||
Target architecture:
|
||||
|
||||
```text
|
||||
ASPX / Master Page / User Controls
|
||||
↓
|
||||
Contracts
|
||||
↓
|
||||
Services
|
||||
↓
|
||||
Repositories / SQL helpers
|
||||
↓
|
||||
Database
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Language rules
|
||||
|
||||
- All new code must be **C#**.
|
||||
- Do not add VB.NET files.
|
||||
- Do not add `.vb` files to the active project.
|
||||
- Old VB files may exist only as migration reference files using `.vb.txt`.
|
||||
- Do not use VB comment syntax in C# files.
|
||||
- Do not use JavaScript/CSS/Powershell comments as C# comments.
|
||||
|
||||
---
|
||||
|
||||
## Command rules
|
||||
|
||||
When giving commands for this project:
|
||||
|
||||
- Prefer **PowerShell**, **Developer PowerShell**, or **Visual Studio Package Manager Console** commands.
|
||||
- Commands must be written in **one line**.
|
||||
- Do not use PowerShell line continuation with backticks.
|
||||
- Do not suggest `dotnet new webforms` because WebForms is not supported by the .NET CLI as an ASP.NET Core project.
|
||||
- If a WebForms project must be created from command line, use Visual Studio/MSBuild-compatible project files or create the project through Visual Studio using **ASP.NET Web Application (.NET Framework)**.
|
||||
- Target framework must be `.NET Framework 4.8` or `.NET Framework 4.8.1`.
|
||||
|
||||
Recommended project creation path:
|
||||
|
||||
```powershell
|
||||
mkdir "C:\Proyectos\Agua de hermosillo\Plataforma de Desarrolladores"
|
||||
```
|
||||
|
||||
```powershell
|
||||
cd "C:\Proyectos\Agua de hermosillo\Plataforma de Desarrolladores"
|
||||
```
|
||||
|
||||
Then create/open the WebForms project in Visual Studio 2022 as:
|
||||
|
||||
```text
|
||||
ASP.NET Web Application (.NET Framework) → Web Forms → C# → .NET Framework 4.8/4.8.1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project folder rules
|
||||
|
||||
Use this base structure:
|
||||
|
||||
```text
|
||||
SIG.Web/
|
||||
AppCode/
|
||||
Infrastructure/
|
||||
Sql/
|
||||
Repositories/
|
||||
Security/
|
||||
Utilities/
|
||||
Contracts/
|
||||
Models/
|
||||
Services/
|
||||
Styles/
|
||||
Scripts/
|
||||
Metronic/
|
||||
Site.master
|
||||
Site.master.cs
|
||||
Login.aspx
|
||||
Login.aspx.cs
|
||||
Inicio.aspx
|
||||
Inicio.aspx.cs
|
||||
BundleConfig.cs
|
||||
Global.asax
|
||||
Global.asax.cs
|
||||
Web.config
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Do not place database access directly inside `.aspx.cs`, `.ascx.cs`, or `Site.master.cs`.
|
||||
- Database-related helpers go under `App_Code/Sql` or a clearly named SQL/data folder.
|
||||
- Business access goes through `Contracts` and `Services`.
|
||||
- If the project file needs explicit compilation entries, include C# files recursively, for example `App_Code\**\*.cs`, but avoid duplicate compile entries that include the same file twice.
|
||||
|
||||
---
|
||||
|
||||
## Asset folder rules
|
||||
|
||||
The following folders are legacy visual dependencies and must be copied as complete folders:
|
||||
|
||||
```text
|
||||
Styles/
|
||||
Scripts/
|
||||
Metronic/
|
||||
```
|
||||
|
||||
Do not replace Metronic with Bootstrap-only layout.
|
||||
Do not upgrade Metronic unless the user explicitly asks.
|
||||
Do not remove old scripts just because they look unused; only remove references from bundles when they are not used by the master/login flow.
|
||||
|
||||
---
|
||||
|
||||
## Bundle rules
|
||||
|
||||
The project must implement only the bundle methods needed by the current `Site.master` and login page.
|
||||
|
||||
Do not bring every old bundle method from the legacy project.
|
||||
Do not add unused bundle groups just because they existed before.
|
||||
Do not add duplicate bundle keys.
|
||||
Do not add empty bundle methods.
|
||||
|
||||
Allowed current bundle keys are the ones actually rendered by master/login, especially:
|
||||
|
||||
```text
|
||||
~/bundles/modernizr
|
||||
~/bundles/scripts
|
||||
~/bundles/highcharts
|
||||
~/Metronic/assets/global/scripts/JSV1
|
||||
~/Metronic/assets/global/css/cssV1
|
||||
```
|
||||
|
||||
WebForms/AjaxControlToolkit support bundles are allowed only when referenced by `ScriptManager` or required by WebForms controls:
|
||||
|
||||
```text
|
||||
~/bundles/jquery
|
||||
~/bundles/WebFormsJs
|
||||
~/bundles/MsAjaxJs
|
||||
~/Content/AjaxControlToolkit/Styles/Bundle
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- If `Site.master` or `Login.aspx` does not render a bundle, do not add it.
|
||||
- If a bundle exists only for old pages not migrated yet, leave it out until the page needs it.
|
||||
- Prefer preserving original asset paths from `Styles`, `Scripts`, and `Metronic`.
|
||||
- Use `CssRewriteUrlTransform` for CSS bundles that reference fonts, images, or relative URLs.
|
||||
- Do not register CSS/JS from server controls when the style/script is global; use `BundleConfig` and the master page.
|
||||
|
||||
---
|
||||
|
||||
## WebForms page rules
|
||||
|
||||
All normal `.aspx` pages must use `Site.master`, except the login page.
|
||||
|
||||
Normal pages:
|
||||
|
||||
- Must include `MasterPageFile="~/Site.master"`.
|
||||
- Must not include `<html>`, `<head>`, `<body>`, or `<form>`.
|
||||
- Must use `<asp:Content>` controls.
|
||||
- Should inherit from `ClsPaginaBase` or the current C# page base class.
|
||||
- Must not connect directly to SQL classes.
|
||||
- Must call contracts/services for data, authentication, permissions, catalogs, menus, and user context.
|
||||
|
||||
Public registration exception:
|
||||
|
||||
- `Pages/Security/RegistroCliente.aspx`, `RegistroPendiente.aspx` and `VerificarCorreo.aspx` may be standalone public pages with their own `form runat="server"`.
|
||||
- `RegistroCliente.aspx` keeps its two-step draft in encrypted ViewState and persists only after all client/company validations pass.
|
||||
- All persistence continues through contracts/services/repositories and stored procedures.
|
||||
|
||||
Login page:
|
||||
|
||||
- Is the only page allowed to have its own full HTML structure.
|
||||
- May inherit from `System.Web.UI.Page` or its own login base class.
|
||||
- Must not inherit from `ClsPaginaBase` if that base class requires an authenticated session.
|
||||
- Must use `IAuthContract` or the current auth contract.
|
||||
- Must create a dummy session until real authentication is implemented.
|
||||
- Must not hardcode real database login logic in `Login.aspx.cs`.
|
||||
|
||||
Important naming rule:
|
||||
|
||||
- Do not create both `Login.aspx` and `LogIn.aspx`.
|
||||
- If the starter already uses `LogIn.aspx`, keep that name and keep `Web.config` consistent.
|
||||
- If creating from scratch, prefer `Login.aspx`, but update `Web.config`, redirects, and links consistently.
|
||||
|
||||
---
|
||||
|
||||
## Master page rules
|
||||
|
||||
`Site.master` owns the main HTML structure.
|
||||
|
||||
Rules:
|
||||
|
||||
- `Site.master` owns the only main `<form runat="server">`.
|
||||
- `Site.master` must keep the Metronic layout structure.
|
||||
- Keep the sidebar/header/page-content structure similar to the original Metronic project.
|
||||
- Do not restore the hidden search box from the old header.
|
||||
- Keep the actions button if it exists in the selected master layout.
|
||||
- Do not hardcode real user/menu database logic inside `Site.master.cs`.
|
||||
- User data must come from a contract/service.
|
||||
- Menu data must come from a contract/service.
|
||||
- Breadcrumb/title/layout data must come from a contract/service when dynamic.
|
||||
- Do not use `<title id="Titulo" runat="server">`.
|
||||
- Prefer `<title><%: Page.Title %></title>`.
|
||||
- If a generated starter currently uses a title literal, replace it with `Page.Title` unless there is a specific reason to keep the literal.
|
||||
|
||||
Current preferred contracts:
|
||||
|
||||
```text
|
||||
IAuthContract
|
||||
IUserSessionContract
|
||||
IMenuContract
|
||||
IMasterPageContract
|
||||
```
|
||||
|
||||
If old names such as `IUserContextProvider` or `IMenuProvider` exist, migrate them to the current contract naming or create adapters. Do not keep two competing naming systems.
|
||||
|
||||
---
|
||||
|
||||
## ContentPlaceHolder rules
|
||||
|
||||
`Site.master` must use short placeholder IDs.
|
||||
|
||||
Preferred final placeholder IDs:
|
||||
|
||||
```text
|
||||
H = page-specific head/CSS content
|
||||
C = main page content
|
||||
J = page-specific JavaScript
|
||||
```
|
||||
|
||||
Example in `Site.master`:
|
||||
|
||||
```aspx
|
||||
<asp:ContentPlaceHolder ID="H" runat="server" />
|
||||
<asp:ContentPlaceHolder ID="C" runat="server" />
|
||||
<asp:ContentPlaceHolder ID="J" runat="server" />
|
||||
```
|
||||
|
||||
Example in a normal page:
|
||||
|
||||
```aspx
|
||||
<asp:Content ID="ContentHead" ContentPlaceHolderID="H" runat="server">
|
||||
</asp:Content>
|
||||
|
||||
<asp:Content ID="ContentMain" ContentPlaceHolderID="C" runat="server">
|
||||
</asp:Content>
|
||||
|
||||
<asp:Content ID="ContentScripts" ContentPlaceHolderID="J" runat="server">
|
||||
</asp:Content>
|
||||
```
|
||||
|
||||
If the generated starter currently has `HeadContent`, `MainContent`, or `ScriptsContent`, rename them to `H`, `C`, and `J` before adding more pages.
|
||||
|
||||
Do not mix placeholder naming systems in the same project.
|
||||
|
||||
---
|
||||
|
||||
## Login and session rules
|
||||
|
||||
Authentication must go through a contract.
|
||||
|
||||
Required flow:
|
||||
|
||||
```text
|
||||
Login.aspx.cs
|
||||
↓
|
||||
IAuthContract
|
||||
↓
|
||||
DummyAuthContract now, real AuthService later
|
||||
↓
|
||||
IUserSessionContract
|
||||
↓
|
||||
Session user
|
||||
```
|
||||
|
||||
Initial login may be dummy.
|
||||
|
||||
Dummy session user should be safe and clearly fake, for example:
|
||||
|
||||
```text
|
||||
UserId: 15
|
||||
CompanyId: 1
|
||||
RoleId: 1
|
||||
RoleName: Administrador
|
||||
CompanyName: Empresa Demo
|
||||
DisplayName: Usuario Demo
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Dummy login may accept any non-empty username and password during starter setup.
|
||||
- Mark dummy authentication clearly in code comments.
|
||||
- Do not mix real SQL authentication with dummy authentication in the same class.
|
||||
- When real login is implemented, create a new service such as `SqlAuthContract` or `AuthService`.
|
||||
- Do not store passwords in session.
|
||||
- Do not log raw passwords.
|
||||
- Use Forms Authentication if staying with classic WebForms auth.
|
||||
|
||||
---
|
||||
|
||||
## Contract rules
|
||||
|
||||
Contracts belong in `/Contracts`.
|
||||
|
||||
Current minimum contracts:
|
||||
|
||||
```text
|
||||
Contracts/IAuthContract.cs
|
||||
Contracts/IUserSessionContract.cs
|
||||
Contracts/IMenuContract.cs
|
||||
Contracts/IMasterPageContract.cs
|
||||
```
|
||||
|
||||
Suggested future contracts:
|
||||
|
||||
```text
|
||||
Contracts/IPermissionContract.cs
|
||||
Contracts/INotificationContract.cs
|
||||
Contracts/ICatalogSearchContract.cs
|
||||
Contracts/IAuditContract.cs
|
||||
Contracts/ICurrentCompanyContract.cs
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Pages call contracts, not SQL classes.
|
||||
- Master page calls contracts, not SQL classes.
|
||||
- Login calls contracts, not SQL classes.
|
||||
- Contracts must return models, DTOs, or result objects, not raw `DataTable` unless migrating legacy code.
|
||||
- If legacy `DataTable` is required, isolate it behind a contract and document why.
|
||||
|
||||
---
|
||||
|
||||
## Service rules
|
||||
|
||||
Services belong in `/Services`.
|
||||
|
||||
Current starter services may include:
|
||||
|
||||
```text
|
||||
Services/ContractRegistry.cs
|
||||
Services/DummyAuthContract.cs
|
||||
Services/UserSessionContract.cs
|
||||
Services/DummyMenuContract.cs
|
||||
Services/MasterPageContract.cs
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- `ContractRegistry` may be used as a simple manual dependency resolver for WebForms.
|
||||
- Do not introduce a complex dependency injection framework unless required.
|
||||
- Services may depend on SQL helpers/repositories.
|
||||
- `.aspx.cs` files should be thin and delegate work to services.
|
||||
- Keep dummy services separate from SQL-backed services.
|
||||
|
||||
---
|
||||
|
||||
## Model rules
|
||||
|
||||
Models belong in `/Models`.
|
||||
|
||||
Current starter models may include:
|
||||
|
||||
```text
|
||||
Models/AppUser.cs
|
||||
Models/LoginResult.cs
|
||||
Models/MenuItemModel.cs
|
||||
Models/MasterLayoutModel.cs
|
||||
Models/BreadcrumbItem.cs
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Use models to move data between contracts and WebForms pages.
|
||||
- Do not pass anonymous objects across layers.
|
||||
- Do not expose SQL connection classes or `SqlDataReader` from services to pages.
|
||||
- Keep models simple and serializable when possible.
|
||||
|
||||
---
|
||||
|
||||
## Database and SQL structure rules
|
||||
|
||||
All database connection structure must be centralized under `App_Code/Sql` or an equivalent SQL/data folder.
|
||||
|
||||
Current starter SQL structure:
|
||||
|
||||
```text
|
||||
App_Code/Sql/ConnectionNames.cs
|
||||
App_Code/Sql/IConnectionStringProvider.cs
|
||||
App_Code/Sql/WebConfigConnectionStringProvider.cs
|
||||
App_Code/Sql/SqlConnectionFactory.cs
|
||||
App_Code/Sql/SqlQueryExecutor.cs
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Define connection names in one place.
|
||||
- Read connection strings from `Web.config`.
|
||||
- Do not hardcode connection strings in pages, master, controls, or services.
|
||||
- Do not create `SqlConnection` directly in `.aspx.cs`.
|
||||
- Do not create `SqlCommand` directly in `.aspx.cs`.
|
||||
- Use repositories or SQL executor classes behind contracts.
|
||||
- All application database reads and writes must use stored procedures; inline SQL in C# is prohibited.
|
||||
- Mandatory: read and follow `AGENTS_STORED_PROCEDURES.md` for naming, output parameters, TRY/CATCH, logging, transactions, and logical deletion.
|
||||
- Mandatory: read and follow `AGENTS_DATA_READER_MAPPING.md` for object, list, `DataTable`, and `JArray` result mapping.
|
||||
- Mandatory: read and follow `AGENTS_DATABASE_ERROR_LOGGING.md` for safe parameter logging and reproducible stored-procedure calls.
|
||||
- Mandatory: read and follow `AGENTS_PAGE_STRUCTURE.md` for the `Pages/Modulo/` location of all new WebForms pages.
|
||||
- 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.
|
||||
- Keep stored procedure names centralized.
|
||||
- Repositories must use `CommandType.StoredProcedure` and parameterized inputs/outputs.
|
||||
- Never concatenate raw user input into SQL.
|
||||
|
||||
Recommended Web.config connection string pattern:
|
||||
|
||||
```xml
|
||||
<connectionStrings>
|
||||
<add name="DefaultConnection" connectionString="..." providerName="System.Data.SqlClient" />
|
||||
<add name="SecurityConnection" connectionString="..." providerName="System.Data.SqlClient" />
|
||||
<add name="ReportingConnection" connectionString="..." providerName="System.Data.SqlClient" />
|
||||
</connectionStrings>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Base page rules
|
||||
|
||||
Normal pages should inherit from a C# base page.
|
||||
|
||||
Preferred base class:
|
||||
|
||||
```text
|
||||
App_Code/Infrastructure/ClsPaginaBase.cs
|
||||
```
|
||||
|
||||
If the starter currently has `WebFormsPageBase.cs`, either:
|
||||
|
||||
- rename it to `ClsPaginaBase.cs`, or
|
||||
- keep `WebFormsPageBase` and update this AGENTS.md/project rules consistently.
|
||||
|
||||
Do not keep both base classes doing the same job.
|
||||
|
||||
Base page responsibilities:
|
||||
|
||||
- Validate authenticated session.
|
||||
- Expose current user from `IUserSessionContract`.
|
||||
- Provide helper methods for messages, redirects, and permissions.
|
||||
- Avoid database access.
|
||||
- Avoid page-specific business logic.
|
||||
|
||||
---
|
||||
|
||||
## Global.asax rules
|
||||
|
||||
`Global.asax` / `Global.asax.cs` should:
|
||||
|
||||
- Register bundles through `BundleConfig.RegisterBundles(BundleTable.Bundles)`.
|
||||
- Configure basic application startup.
|
||||
- Avoid business logic.
|
||||
- Avoid direct database startup queries unless absolutely required.
|
||||
|
||||
If OWIN is not used, prevent startup errors by adding this to `Web.config`:
|
||||
|
||||
```xml
|
||||
<add key="owin:AutomaticAppStartup" value="false" />
|
||||
```
|
||||
|
||||
Do not add OWIN packages unless the project actually needs OWIN.
|
||||
|
||||
---
|
||||
|
||||
## Web.config rules
|
||||
|
||||
`Web.config` must stay compatible with ASP.NET WebForms on .NET Framework 4.8/4.8.1.
|
||||
|
||||
Rules:
|
||||
|
||||
- Use Forms Authentication unless a different auth system is explicitly requested.
|
||||
- Allow anonymous access only to the login page and required static assets.
|
||||
- Do not add ASP.NET Core middleware settings.
|
||||
- Do not add unsupported `.NET Core` config sections.
|
||||
- Do not add CodeDom provider entries unless the matching NuGet package is installed.
|
||||
- If this error appears: `No se pudo ubicar el tipo de proveedor CodeDom Microsoft.CodeDom.Providers.DotNetCompilerPlatform...`, either install the exact package version or remove/fix the `system.codedom` section.
|
||||
- Keep MIME mappings for fonts such as `.woff` and `.woff2` when Metronic requires them.
|
||||
|
||||
---
|
||||
|
||||
## OWIN rule
|
||||
|
||||
If the application shows this error:
|
||||
|
||||
```text
|
||||
No assembly found containing an OwinStartupAttribute
|
||||
```
|
||||
|
||||
And the project does not use OWIN, add this in `appSettings`:
|
||||
|
||||
```xml
|
||||
<add key="owin:AutomaticAppStartup" value="false" />
|
||||
```
|
||||
|
||||
Do not create an empty OWIN startup class just to silence the error unless OWIN is actually required.
|
||||
|
||||
---
|
||||
|
||||
## Site.master rendering rules
|
||||
|
||||
The master page should render bundles and scripts in this general order:
|
||||
|
||||
Inside `<head>`:
|
||||
|
||||
```aspx
|
||||
<%: Scripts.Render("~/bundles/modernizr") %>
|
||||
<%: Styles.Render("~/Content/AjaxControlToolkit/Styles/Bundle") %>
|
||||
<%: Styles.Render("~/Metronic/assets/global/css/cssV1") %>
|
||||
<asp:ContentPlaceHolder ID="H" runat="server" />
|
||||
```
|
||||
|
||||
Inside `<form>` near the top:
|
||||
|
||||
```aspx
|
||||
<asp:ScriptManager ID="TSM" runat="server" />
|
||||
<%: Scripts.Render("~/bundles/scripts") %>
|
||||
```
|
||||
|
||||
Before closing `</form>`:
|
||||
|
||||
```aspx
|
||||
<%: Scripts.Render("~/Metronic/assets/global/scripts/JSV1") %>
|
||||
<%: Scripts.Render("~/bundles/highcharts") %>
|
||||
<asp:ContentPlaceHolder ID="J" runat="server" />
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Do not render the same script twice.
|
||||
- Do not render JQuery twice.
|
||||
- Keep `ScriptManager` if AjaxControlToolkit, UpdatePanel, or WebForms AJAX is used.
|
||||
- Keep `TSM_AsyncPostBackError` only if implemented in code-behind.
|
||||
- Remove or implement any event handler referenced by markup.
|
||||
|
||||
---
|
||||
|
||||
## Header/menu rules
|
||||
|
||||
The master header must preserve the selected Metronic layout.
|
||||
|
||||
Rules:
|
||||
|
||||
- Keep user dropdown.
|
||||
- Keep logout option.
|
||||
- Keep actions button when present.
|
||||
- Do not restore the hidden/old search box from legacy header.
|
||||
- Sidebar menu must be built from `IMenuContract`.
|
||||
- Do not build the menu from SQL inside `Site.master.cs`.
|
||||
- Menu items should use `MenuItemModel` or an equivalent model.
|
||||
- Encode text output when generating HTML menu strings.
|
||||
|
||||
---
|
||||
|
||||
## Page title and breadcrumb rules
|
||||
|
||||
Page title and breadcrumb should be handled consistently.
|
||||
|
||||
Rules:
|
||||
|
||||
- Use `Page.Title` for the browser title.
|
||||
- Use a master/layout model for visible page title and breadcrumb.
|
||||
- Do not hardcode global breadcrumb logic in every page.
|
||||
- Normal pages may set page title in `Page_Load` or via properties.
|
||||
|
||||
Preferred model:
|
||||
|
||||
```text
|
||||
MasterLayoutModel
|
||||
PageTitle
|
||||
Breadcrumbs
|
||||
CurrentUser
|
||||
MenuItems
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security rules
|
||||
|
||||
- Do not store passwords in code, comments, Web.config examples, or session.
|
||||
- Do not expose real connection strings in committed files.
|
||||
- Use dummy values in examples.
|
||||
- Use parameterized SQL.
|
||||
- Validate uploaded files by extension, MIME type, and size when uploads are later added.
|
||||
- Avoid writing raw exception details to the user in production.
|
||||
- Keep `customErrors mode="Off"` only for local development.
|
||||
|
||||
---
|
||||
|
||||
## Error handling rules
|
||||
|
||||
- Handle null session user cleanly and redirect to login.
|
||||
- Handle failed login with a friendly message.
|
||||
- Handle async postback errors through `ScriptManager.AsyncPostBackError` only if the handler exists.
|
||||
- Avoid infinite loading states in JavaScript controls.
|
||||
- For AJAX/server search controls, use client and server timeouts.
|
||||
- Recommended default search timeout: about 20 to 30 seconds unless the user asks otherwise.
|
||||
|
||||
---
|
||||
|
||||
## JavaScript and CSS rules
|
||||
|
||||
- Global scripts/styles must be registered in `BundleConfig` and rendered by master.
|
||||
- Page-specific scripts go in placeholder `J`.
|
||||
- Page-specific CSS/head content goes in placeholder `H`.
|
||||
- Do not inline large JavaScript blocks in `.aspx` unless page-specific and small.
|
||||
- Do not remove existing custom legacy files unless the user asks.
|
||||
- Fix null `addEventListener` errors by checking that the element exists before attaching events.
|
||||
- Avoid blocking Ctrl+V unless a specific feature requires it.
|
||||
|
||||
---
|
||||
|
||||
## User control rules
|
||||
|
||||
If server controls are migrated:
|
||||
|
||||
- Prefer C# classes under `App_Code/SigControls` when creating reusable server controls.
|
||||
- Do not mix WebForms `.ascx` user controls with App_Code server-control classes unless there is a clear reason.
|
||||
- Do not register global CSS/JS from the control when master bundles already include them.
|
||||
- Controls should expose properties/events and avoid direct SQL access.
|
||||
- Controls should call contracts/services when they need data.
|
||||
|
||||
---
|
||||
|
||||
## PDF viewer migration rules
|
||||
|
||||
If the PDF viewer is added to this project later:
|
||||
|
||||
- Implement it in C#.
|
||||
- Prefer `App_Code/SigControls` for server controls.
|
||||
- Do not add VB files.
|
||||
- Do not use `/* */` comments in VB reference files; VB references must remain `.vb.txt` only.
|
||||
- Keep global PDF viewer CSS/JS references in bundles.
|
||||
- Do not register duplicate resources from the control and master at the same time.
|
||||
- Session or temp-folder PDF storage must be explicit and centralized.
|
||||
|
||||
---
|
||||
|
||||
## Home page rules
|
||||
|
||||
Use only one active home page convention.
|
||||
|
||||
Allowed options:
|
||||
|
||||
```text
|
||||
Default.aspx
|
||||
Inicio.aspx
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Do not keep both as competing entry pages.
|
||||
- If using `Inicio.aspx`, redirect `Default.aspx` to `Inicio.aspx` or update Forms Authentication default URL.
|
||||
- If using `Default.aspx`, keep Web.config and menu links pointing to `Default.aspx`.
|
||||
- The home page must use `Site.master`.
|
||||
|
||||
---
|
||||
|
||||
## File delivery rules for this project
|
||||
|
||||
When generating a single downloadable file:
|
||||
|
||||
- If the file is `.js` or `.css` and it is not inside a ZIP, append `.txt` to the filename.
|
||||
- Example: `custom.js.txt`, `styles.css.txt`.
|
||||
- If the file is inside a ZIP, keep the real extension `.js` or `.css`.
|
||||
- For project-level delivery, prefer ZIP files.
|
||||
|
||||
---
|
||||
|
||||
## What not to add yet
|
||||
|
||||
Do not add the following until the user explicitly asks:
|
||||
|
||||
- Real SQL login implementation
|
||||
- Full permission system
|
||||
- Notifications
|
||||
- Web Push
|
||||
- PDF viewer
|
||||
- BuscadorCatalogo
|
||||
- DropdownGrid
|
||||
- PorcentajeBox
|
||||
- Angular
|
||||
- APIs
|
||||
- Background jobs
|
||||
- OWIN
|
||||
- Entity Framework
|
||||
- Dependency injection framework
|
||||
- New Metronic version
|
||||
|
||||
The starter must stay small and focused: commands, master page, login, contracts, dummy user session, bundle structure, copied assets, and centralized SQL foundation.
|
||||
|
||||
---
|
||||
|
||||
## Current implementation checklist
|
||||
|
||||
When modifying or regenerating the project, verify:
|
||||
|
||||
- [ ] Project targets .NET Framework 4.8 or 4.8.1.
|
||||
- [ ] All new code is C#.
|
||||
- [ ] No active `.vb` files were added.
|
||||
- [ ] `Styles`, `Scripts`, and `Metronic` were copied as-is.
|
||||
- [ ] `BundleConfig.cs` contains only bundles rendered by master/login or required by WebForms AJAX.
|
||||
- [ ] `Site.master` owns the only main form.
|
||||
- [ ] Normal pages use `Site.master`.
|
||||
- [ ] Normal pages do not contain full HTML structure.
|
||||
- [ ] Login is the only standalone full HTML page.
|
||||
- [ ] User/session data comes from `IUserSessionContract`.
|
||||
- [ ] Login uses `IAuthContract`.
|
||||
- [ ] Menu comes from `IMenuContract`.
|
||||
- [ ] Master layout data comes from `IMasterPageContract`.
|
||||
- [ ] SQL connection helpers are centralized under `App_Code/Sql`.
|
||||
- [ ] Pages do not directly instantiate SQL connections.
|
||||
- [ ] Placeholder IDs are `H`, `C`, and `J`.
|
||||
- [ ] No hidden old search box was restored.
|
||||
- [ ] Actions button is preserved when present in the selected layout.
|
||||
- [ ] `Web.config` login URL matches the actual login page filename.
|
||||
- [ ] No duplicate `Login.aspx` / `LogIn.aspx` pages exist.
|
||||
- [ ] No OWIN startup error exists, or OWIN automatic startup is disabled.
|
||||
- [ ] No missing CodeDom provider error exists.
|
||||
|
||||
---
|
||||
|
||||
## Preferred next evolution
|
||||
|
||||
After the starter works, the safest growth path is:
|
||||
|
||||
1. Replace dummy login with SQL-backed `IAuthContract`.
|
||||
2. Replace dummy menu with SQL-backed `IMenuContract`.
|
||||
3. Add permission contract.
|
||||
4. Add base page authorization checks.
|
||||
5. Add reusable controls one by one.
|
||||
6. Add catalog search and UI helpers.
|
||||
7. Add PDF viewer only after the base shell is stable.
|
||||
|
||||
Keep every new feature behind contracts so the WebForms UI remains thin and easier to migrate later if needed.
|
||||
63
AGENTS_DATABASE_ERROR_LOGGING.md
Normal file
63
AGENTS_DATABASE_ERROR_LOGGING.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Reglas obligatorias para registrar errores de base de datos
|
||||
|
||||
Estas reglas complementan `AGENTS.md`, `AGENTS_STORED_PROCEDURES.md` y `AGENTS_DATA_READER_MAPPING.md`.
|
||||
|
||||
## Información requerida
|
||||
|
||||
Cuando falle una llamada a un procedimiento almacenado, el log técnico debe incluir:
|
||||
|
||||
- Nombre completo del procedimiento, incluyendo esquema.
|
||||
- Nombre de cada parámetro.
|
||||
- Valor recibido por cada parámetro de entrada.
|
||||
- Tipo y valor disponible de los parámetros de salida.
|
||||
- Mensaje y número de la excepción SQL.
|
||||
- Un bloque `ReplaySql` que pueda copiarse en SQL Server para reproducir la llamada.
|
||||
|
||||
Formato mínimo:
|
||||
|
||||
```text
|
||||
StoredProcedure: dbo.PA_Modulo_Accion
|
||||
Parametros:
|
||||
@CveProyecto=-1
|
||||
@CvePeriodo=0
|
||||
@EsSimulador=0
|
||||
ReplaySql:
|
||||
DECLARE @ErrorMostrar varchar(max);
|
||||
DECLARE @ErrorTipo int;
|
||||
DECLARE @Return bit;
|
||||
EXEC [dbo].[PA_Modulo_Accion]
|
||||
@CveProyecto = -1,
|
||||
@CvePeriodo = 0,
|
||||
@EsSimulador = 0,
|
||||
@ErrorMostrar = @ErrorMostrar OUTPUT,
|
||||
@ErrorTipo = @ErrorTipo OUTPUT,
|
||||
@Return = @Return OUTPUT;
|
||||
SELECT @ErrorMostrar, @ErrorTipo, @Return;
|
||||
```
|
||||
|
||||
## Seguridad
|
||||
|
||||
- Nunca registrar valores de contraseñas, hashes, tokens, secretos, API keys ni cadenas de conexión.
|
||||
- Sustituir esos valores con `[REDACTADO]` tanto en `Parametros` como en `ReplaySql`.
|
||||
- El SQL reproducible con valores redactados requiere que el desarrollador proporcione manualmente un valor seguro de prueba.
|
||||
- Escapar comillas simples y generar literales SQL válidos.
|
||||
- Limitar textos extensos y marcar el valor como truncado.
|
||||
- No mostrar `ReplaySql`, parámetros ni detalles técnicos al usuario final.
|
||||
- Los datos técnicos sólo deben escribirse en el archivo log protegido de la aplicación.
|
||||
|
||||
## Implementación
|
||||
|
||||
- Configurar todos los comandos mediante `StoredProcedureCommand` para que `StoredProcedureDiagnostics` capture el contexto.
|
||||
- `DbExecutor` debe adjuntar el diagnóstico al log en `SqlException`, timeout, error de salida del PA y excepciones inesperadas.
|
||||
- Limpiar el contexto de diagnóstico en `finally` para impedir que parámetros de una petición aparezcan en otra.
|
||||
- Usar `AsyncLocal` para aislar correctamente solicitudes concurrentes y operaciones asíncronas.
|
||||
- Capturar los parámetros OUTPUT después de cerrar el `DataReader`.
|
||||
- No establecer `SqlConnection.FireInfoMessageEventOnUserErrors = true`; esa opción transforma errores SQL de severidad 11–16 en eventos informativos y puede ocultar la excepción real.
|
||||
- Los mensajes `RAISERROR` de severidad 10 pueden registrarse mediante `SqlConnection.InfoMessage` sin activar esa opción.
|
||||
- Si `@Return`, `@ErrorTipo` y `@ErrorMostrar` regresan sin valor, registrar que la versión instalada del PA no devolvió el contrato requerido.
|
||||
|
||||
## Alcance
|
||||
|
||||
- Registrar parámetros detallados sólo cuando exista un error o advertencia que requiera diagnóstico.
|
||||
- No registrar cada ejecución exitosa de manera predeterminada.
|
||||
- No usar el log como auditoría de operaciones exitosas; la auditoría pertenece a su propio contrato o procedimiento.
|
||||
100
AGENTS_DATA_READER_MAPPING.md
Normal file
100
AGENTS_DATA_READER_MAPPING.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Reglas obligatorias de mapeo de resultados
|
||||
|
||||
Estas reglas complementan `AGENTS.md` y `AGENTS_STORED_PROCEDURES.md`.
|
||||
|
||||
## Extensión estándar
|
||||
|
||||
- Todo resultado obtenido con `IDataReader` debe convertirse mediante `AppCode/Utilities/DataReaderExtensions.cs`.
|
||||
- No repetir bloques manuales como `Propiedad = Convert.ToInt32(reader["Columna"])` en repositorios o servicios.
|
||||
- Resultados admitidos:
|
||||
|
||||
```csharp
|
||||
T item = reader.ToObject<T>();
|
||||
List<T> items = reader.ToList<T>();
|
||||
DataTable table = reader.ToDataTable();
|
||||
JArray json = reader.ToJsonArray();
|
||||
```
|
||||
|
||||
- `ToObject<T>()` devuelve `null` cuando el procedimiento no devuelve filas.
|
||||
- `ToList<T>()` devuelve una lista vacía cuando no hay filas; no devuelve `null`.
|
||||
- `ToDataTable()` devuelve un `DataTable` vacío cuando no hay filas.
|
||||
- `ToJsonArray()` devuelve un `JArray` vacío cuando no hay filas.
|
||||
|
||||
## Operaciones asíncronas
|
||||
|
||||
- Para código nuevo, preferir las versiones asíncronas cuando el proveedor exponga `DbCommand` y `DbDataReader`.
|
||||
- Métodos disponibles: `ToObjectAsync<T>()`, `ToListAsync<T>()`, `ToDataTableAsync()` y `ToJsonArrayAsync()`.
|
||||
- Los métodos asíncronos deben recibir y propagar un `CancellationToken` cuando la llamada superior disponga de uno.
|
||||
- Abrir el lector con `ExecuteReaderAsync`; no envolver operaciones síncronas de base de datos en `Task.Run`.
|
||||
- Usar `ConfigureAwait(false)` dentro de infraestructura, repositorios y servicios que no necesiten el contexto de WebForms.
|
||||
- Cerrar o desechar el lector antes de leer `@ErrorMostrar`, `@ErrorTipo` y `@Return`.
|
||||
- Una página WebForms que espere directamente debe declarar `Async="true"` y usar `RegisterAsyncTask` o un evento asíncrono compatible.
|
||||
- Mantener los métodos síncronos únicamente para flujos existentes que todavía no se hayan migrado de extremo a extremo.
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```csharp
|
||||
List<UsuarioModel> usuarios;
|
||||
|
||||
using (DbDataReader reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
usuarios = await reader.ToListAsync<UsuarioModel>(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
StoredProcedureCommand.EnsureSuccess(command);
|
||||
return usuarios;
|
||||
```
|
||||
|
||||
## Convención de columnas y propiedades
|
||||
|
||||
- El mapeo se realiza por nombre de columna y propiedad sin distinguir mayúsculas/minúsculas.
|
||||
- Los procedimientos deben usar alias para que las columnas coincidan con el modelo.
|
||||
- Cuando no sea posible usar el mismo nombre, decorar la propiedad con `[Column("NombreColumna")]`.
|
||||
- Las columnas adicionales se ignoran.
|
||||
- Las propiedades sin columna conservan su valor predeterminado.
|
||||
- `DBNull` se convierte en `null` para propiedades anulables o de referencia; para propiedades no anulables conserva el valor predeterminado.
|
||||
- Se admiten tipos anulables, enumeraciones, `Guid`, `TimeSpan`, `DateTimeOffset`, booleanos y conversiones primitivas.
|
||||
- Una conversión incompatible debe producir una excepción para que `DbExecutor` la registre; no ocultar errores de contrato entre el PA y el modelo.
|
||||
|
||||
## Uso correcto con procedimientos almacenados
|
||||
|
||||
- El repositorio crea y configura el comando mediante `StoredProcedureCommand`.
|
||||
- El repositorio abre el lector dentro de `using`.
|
||||
- Debe cerrar o desechar el lector antes de llamar `StoredProcedureCommand.EnsureSuccess(command)`, porque los parámetros OUTPUT se reciben al finalizar el lector.
|
||||
- La extensión sólo mapea datos; no abre conexiones, no crea comandos, no captura excepciones y no decide mensajes para el usuario.
|
||||
- Páginas ASPX, controles y master pages no deben manejar `IDataReader` directamente.
|
||||
|
||||
Ejemplo para un objeto:
|
||||
|
||||
```csharp
|
||||
UserModel user;
|
||||
|
||||
using (IDataReader reader = command.ExecuteReader())
|
||||
{
|
||||
user = reader.ToObject<UserModel>();
|
||||
}
|
||||
|
||||
StoredProcedureCommand.EnsureSuccess(command);
|
||||
return user;
|
||||
```
|
||||
|
||||
Ejemplo para una lista:
|
||||
|
||||
```csharp
|
||||
List<MenuItemModel> items;
|
||||
|
||||
using (IDataReader reader = command.ExecuteReader())
|
||||
{
|
||||
items = reader.ToList<MenuItemModel>();
|
||||
}
|
||||
|
||||
StoredProcedureCommand.EnsureSuccess(command);
|
||||
return items;
|
||||
```
|
||||
|
||||
## Elección del tipo de resultado
|
||||
|
||||
- Preferir modelos tipados para reglas de negocio, seguridad, usuarios, permisos, menús y módulos nuevos.
|
||||
- Usar `DataTable` sólo para compatibilidad con componentes WebForms o migración de código heredado que realmente lo requiera.
|
||||
- Usar `JArray` únicamente cuando el contrato de salida sea JSON dinámico y no exista un modelo estable.
|
||||
- No convertir primero a `DataTable` para después crear objetos o JSON; mapear directamente desde `IDataReader`.
|
||||
164
AGENTS_JAVASCRIPT_INITIALIZATION.md
Normal file
164
AGENTS_JAVASCRIPT_INITIALIZATION.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Reglas obligatorias para inicialización JavaScript idempotente
|
||||
|
||||
Estas reglas complementan `AGENTS.md` y `AGENTS_WEBFORMS_PARTIAL_POSTBACKS.md`.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Los inicializadores deben poder ejecutarse después de la carga inicial y después de cualquier postback parcial sin:
|
||||
|
||||
- inicializar dos veces el mismo control;
|
||||
- duplicar eventos;
|
||||
- destruir y reconstruir controles que siguen vigentes;
|
||||
- modificar elementos que ya fueron procesados;
|
||||
- producir errores porque un plugin ya existe.
|
||||
|
||||
## Banderas por elemento
|
||||
|
||||
- Cada control inicializado debe conservar una bandera en el propio elemento HTML.
|
||||
- Los inicializadores deben seleccionar únicamente elementos que todavía no tengan su bandera.
|
||||
- Para código nuevo, usar atributos con namespace `data-sig-<componente>-initialized="1"`.
|
||||
- La bandera debe identificar el componente, no usar nombres genéricos que puedan colisionar.
|
||||
|
||||
Ejemplos:
|
||||
|
||||
```html
|
||||
data-sig-select2-initialized="1"
|
||||
data-sig-tooltip-initialized="1"
|
||||
data-sig-dropzone-initialized="1"
|
||||
data-sig-datepicker-initialized="1"
|
||||
```
|
||||
|
||||
- `custom.js` ya contiene banderas heredadas como `loaded`, `creado` y `ADsortable`. Deben respetarse mientras sus inicializadores sigan activos.
|
||||
- Al modificar completamente uno de esos inicializadores, puede migrarse a `data-sig-*`, pero deben actualizarse juntos todos sus selectores, lecturas, escrituras y rutinas de destrucción.
|
||||
- Para banderas nuevas visibles en el DOM, preferir `.attr('data-sig-...', '1')`, `.is('[data-sig-...]')` o `element.dataset`.
|
||||
- No mezclar `.data()` y `.attr()` para leer/escribir la misma bandera: jQuery mantiene un caché interno y ambos valores pueden quedar diferentes.
|
||||
|
||||
## Momento correcto para colocar la bandera
|
||||
|
||||
- Comprobar la bandera antes de modificar el elemento o registrar eventos.
|
||||
- Colocar la bandera sólo después de que el plugin o control termine de inicializar correctamente.
|
||||
- Si la inicialización falla, el elemento debe permanecer sin bandera para permitir un reintento posterior.
|
||||
- No colocar la bandera antes de una operación que pueda lanzar una excepción.
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```javascript
|
||||
function initializeSelect2(root) {
|
||||
var $root = $(root || document);
|
||||
|
||||
$root.find('.js-select2:not([data-sig-select2-initialized="1"])')
|
||||
.addBack('.js-select2:not([data-sig-select2-initialized="1"])')
|
||||
.each(function () {
|
||||
var $control = $(this);
|
||||
|
||||
$control.select2({
|
||||
width: '100%'
|
||||
});
|
||||
|
||||
$control.attr('data-sig-select2-initialized', '1');
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Postbacks parciales y contenido nuevo
|
||||
|
||||
- Cuando un `UpdatePanel` reemplaza su HTML, los elementos nuevos no contienen la bandera de la instancia anterior y serán inicializados normalmente.
|
||||
- Los elementos fuera del panel actualizado conservan su bandera y deben ignorarse.
|
||||
- Siempre que sea posible, pasar como `root` únicamente el panel actualizado en lugar de volver a recorrer todo `document`.
|
||||
- Centralizar la conexión con `Sys.WebForms.PageRequestManager.endRequest` o `Sys.Application.load`; no registrar ese puente desde cada página.
|
||||
- El puente global debe registrarse una sola vez.
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```javascript
|
||||
function initializeSigControls(root) {
|
||||
initializeSelect2(root);
|
||||
initializeTooltips(root);
|
||||
initializeDatePickers(root);
|
||||
}
|
||||
|
||||
if (window.SIG_PARTIAL_INITIALIZERS_REGISTERED !== true) {
|
||||
window.SIG_PARTIAL_INITIALIZERS_REGISTERED = true;
|
||||
|
||||
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(
|
||||
function (sender, args) {
|
||||
var panels = args.get_panelsUpdated();
|
||||
|
||||
if (!panels || panels.length === 0) {
|
||||
initializeSigControls(document);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var index = 0; index < panels.length; index++) {
|
||||
initializeSigControls(panels[index]);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Eventos delegados y eventos directos
|
||||
|
||||
- Los eventos delegados sobre `body`, `document` o un contenedor permanente deben registrarse una sola vez mediante una bandera global.
|
||||
- `custom.js` ya usa `window.SIG_CUSTOM_READY_INITIALIZED`; conservar este patrón para el bloque global existente.
|
||||
- No ejecutar repetidamente `.off().on()` sobre todos los controles después de cada postback parcial.
|
||||
- Para eventos directos inevitables, inicializar únicamente elementos sin bandera y usar eventos con namespace.
|
||||
|
||||
Ejemplo de evento directo:
|
||||
|
||||
```javascript
|
||||
function initializeAmountInputs(root) {
|
||||
$(root || document)
|
||||
.find('.js-amount:not([data-sig-amount-initialized="1"])')
|
||||
.each(function () {
|
||||
var $input = $(this);
|
||||
|
||||
$input.on('change.sigAmount', function () {
|
||||
// Manejar cambio.
|
||||
});
|
||||
|
||||
$input.attr('data-sig-amount-initialized', '1');
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Destrucción y reinicialización explícita
|
||||
|
||||
- No quitar una bandera sólo para forzar que el inicializador vuelva a ejecutarse.
|
||||
- Quitar la bandera únicamente cuando exista una necesidad real de destruir o reconfigurar el componente.
|
||||
- Antes de quitarla, llamar la API de destrucción del plugin y eliminar eventos con su namespace.
|
||||
- El orden obligatorio es: destruir plugin, retirar eventos propios, quitar bandera y finalmente reinicializar si corresponde.
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```javascript
|
||||
function destroySelect2(control) {
|
||||
var $control = $(control);
|
||||
|
||||
if ($control.attr('data-sig-select2-initialized') !== '1') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($control.hasClass('select2-hidden-accessible')) {
|
||||
$control.select2('destroy');
|
||||
}
|
||||
|
||||
$control.off('.sigSelect2');
|
||||
$control.removeAttr('data-sig-select2-initialized');
|
||||
}
|
||||
```
|
||||
|
||||
## Clonado y reemplazo de controles
|
||||
|
||||
- No copiar accidentalmente banderas de inicialización al clonar controles.
|
||||
- Preferir clonar la plantilla antes de inicializarla.
|
||||
- Si se clona un elemento ya inicializado, retirar del clon las banderas `data-sig-*-initialized`, IDs generados por plugins y contenedores auxiliares antes de insertarlo.
|
||||
- Un control eliminado por un `UpdatePanel` no necesita limpieza manual de bandera, pero los recursos globales o listeners externos sí deben liberarse cuando el plugin lo requiera.
|
||||
|
||||
## Organización
|
||||
|
||||
- Cada inicializador debe ser una función pequeña e idempotente que acepte un elemento raíz.
|
||||
- La función central sólo coordina inicializadores; no debe contener toda la lógica de cada plugin.
|
||||
- No duplicar inicializadores dentro de páginas `.aspx` si pertenecen globalmente a `Scripts/custom.js`.
|
||||
- El JavaScript específico de una página puede vivir en el placeholder `J`, pero debe seguir las mismas reglas de banderas y postbacks parciales.
|
||||
- No modificar archivos minificados ni dependencias de terceros para implementar estas banderas.
|
||||
88
AGENTS_PAGE_STRUCTURE.md
Normal file
88
AGENTS_PAGE_STRUCTURE.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Reglas obligatorias para la ubicación de páginas WebForms
|
||||
|
||||
Estas reglas complementan `AGENTS.md` y aplican a toda página `.aspx` nueva.
|
||||
|
||||
## Raíz de páginas
|
||||
|
||||
- `SIG.Web/Pages/` es la raíz obligatoria para todas las páginas funcionales nuevas.
|
||||
- No agregar páginas de aplicación directamente dentro de `SIG.Web/Pages/`; cada página debe pertenecer a una subcarpeta de módulo.
|
||||
- Usar nombres de carpeta en PascalCase y conservar un solo nombre por módulo.
|
||||
|
||||
Estructura inicial:
|
||||
|
||||
```text
|
||||
SIG.Web/
|
||||
Pages/
|
||||
Security/
|
||||
Tramites/
|
||||
Polizas/
|
||||
```
|
||||
|
||||
Ejemplos futuros:
|
||||
|
||||
```text
|
||||
Pages/Security/Usuarios.aspx
|
||||
Pages/Security/Roles.aspx
|
||||
Pages/Tramites/Solicitudes.aspx
|
||||
Pages/Tramites/Seguimiento.aspx
|
||||
Pages/Polizas/PrePolizas.aspx
|
||||
```
|
||||
|
||||
- Crear nuevas carpetas bajo `Pages/` cuando aparezca un módulo real, por ejemplo `Clientes`, `Fraccionamientos`, `Ventas`, `Catalogos` o `Reportes`.
|
||||
- No crear carpetas vacías para módulos que todavía no existen, salvo las carpetas iniciales acordadas.
|
||||
- No colocar páginas de la aplicación dentro de `Metronic`, `Scripts`, `Styles`, `Content`, `AppCode` ni carpetas de paquetes o recursos de terceros.
|
||||
- Los ejemplos `.aspx` incluidos dentro de dependencias visuales de `Metronic` son archivos heredados del proveedor y no cuentan como páginas funcionales de la aplicación. No usarlos como destino para código nuevo.
|
||||
|
||||
## Únicas páginas permitidas fuera de Pages
|
||||
|
||||
Las páginas de aplicación que actualmente están en la raíz son excepciones explícitas y son las únicas que pueden permanecer o existir fuera de `Pages/`:
|
||||
|
||||
```text
|
||||
AccessDenied.aspx
|
||||
Inicio.aspx
|
||||
Login.aspx
|
||||
Logout.aspx
|
||||
```
|
||||
|
||||
- No agregar otra página `.aspx` en la raíz de `SIG.Web`.
|
||||
- No mover estas cuatro páginas sin una solicitud explícita, porque sus rutas forman parte del inicio, autenticación y manejo global de acceso.
|
||||
- `Login.aspx` sigue siendo la única página con estructura HTML completa propia.
|
||||
- `Inicio.aspx` y `AccessDenied.aspx` usan `Site.master`.
|
||||
- `Logout.aspx` puede permanecer como endpoint WebForms sin contenido visual.
|
||||
|
||||
## Archivos relacionados
|
||||
|
||||
- Mantener juntos los tres archivos de cada página:
|
||||
|
||||
```text
|
||||
Pages/Modulo/Pagina.aspx
|
||||
Pages/Modulo/Pagina.aspx.cs
|
||||
Pages/Modulo/Pagina.aspx.designer.cs
|
||||
```
|
||||
|
||||
- Incluir los tres archivos en `SIG.Web.csproj` con `DependentUpon` correcto.
|
||||
- Usar namespaces que reflejen la carpeta, por ejemplo `SIG.Web.Pages.Security`.
|
||||
- El atributo `Inherits` del `.aspx` debe coincidir exactamente con el namespace y la clase del code-behind.
|
||||
- Todas las páginas normales deben usar `MasterPageFile="~/Site.master"` y los placeholders `H`, `C` y `J`.
|
||||
- Todas las páginas normales deben heredar de `ClsPaginaBase`, salvo una excepción global documentada como Login, Logout o AccessDenied.
|
||||
|
||||
## Rutas y permisos
|
||||
|
||||
- Usar rutas virtuales completas, por ejemplo `~/Pages/Security/Usuarios.aspx`.
|
||||
- Actualizar menús, módulos, permisos, redirecciones y breadcrumbs con la ruta real de la subcarpeta.
|
||||
- No depender de rutas relativas como `../` para navegación de la aplicación.
|
||||
- `PermissionService` y los procedimientos de acceso deben comparar la ruta incluyendo `Pages/Modulo/`.
|
||||
- Los enlaces generados deben pasar por `ResolveUrl`, `VirtualPathUtility.ToAbsolute` o el mecanismo central existente.
|
||||
|
||||
## Arquitectura
|
||||
|
||||
- La ubicación dentro de `Pages/` no autoriza acceso directo a SQL.
|
||||
- El code-behind debe permanecer delgado y llamar contratos/servicios.
|
||||
- Toda operación de base de datos continúa usando procedimientos almacenados mediante repositorios.
|
||||
- Los modelos, contratos, servicios y repositorios no deben colocarse dentro de `Pages/`; permanecen bajo sus carpetas de `AppCode`.
|
||||
|
||||
|
||||
## Excepciones públicas de registro
|
||||
|
||||
Además de Login.aspx, las páginas Pages/Security/RegistroCliente.aspx, Pages/Security/RegistroPendiente.aspx y Pages/Security/VerificarCorreo.aspx pueden ser páginas públicas completas con su propio form runat="server". Son las únicas excepciones nuevas; las páginas autenticadas continúan usando Site.master y no declaran form.
|
||||
RegistroCliente.aspx debe manejar el borrador de ambos pasos en ViewState cifrado y no persistir información hasta terminar todas las validaciones. La persistencia final debe usar servicios/repositorios y un procedimiento almacenado transaccional.
|
||||
123
AGENTS_STORED_PROCEDURES.md
Normal file
123
AGENTS_STORED_PROCEDURES.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Reglas obligatorias de procedimientos almacenados
|
||||
|
||||
Estas reglas complementan `AGENTS.md` y son obligatorias para toda consulta nueva o modificada en la aplicación.
|
||||
|
||||
## Acceso a datos
|
||||
|
||||
- Todas las lecturas y escrituras de la aplicación deben usar procedimientos almacenados.
|
||||
- No escribir `SELECT`, `INSERT`, `UPDATE`, `DELETE` ni `MERGE` directamente en C# dentro de repositorios, servicios, páginas, controles o la master page.
|
||||
- Los repositorios deben usar `CommandType.StoredProcedure`.
|
||||
- Todos los valores proporcionados por la aplicación deben enviarse como parámetros.
|
||||
- Nunca concatenar entrada del usuario en SQL.
|
||||
|
||||
## Nombres
|
||||
|
||||
- Usar el formato `PA_<Tipo>_<Entidad><Accion>`.
|
||||
- `PA` significa procedimiento almacenado.
|
||||
- `<Tipo>` identifica el grupo funcional: `Sis`, `Tra`, `Vta`, `Conta`, `Clientes`, `Fraccionamientos`, etcétera.
|
||||
- `<Entidad>` identifica el objeto y `<Accion>` describe claramente la operación; por ejemplo `Add`, `Update`, `List`, `Activate`, `Deactivate` o `Delete`.
|
||||
- Ejemplos: `PA_Sis_UsuarioAdd`, `PA_Sis_UsuarioUpdate`, `PA_Sis_UsuarioList`, `PA_Sis_UsuarioActivate`, `PA_Sis_UsuarioDeactivate` y `PA_Sis_UsuarioDelete`.
|
||||
|
||||
## Parámetros y resultado obligatorios
|
||||
|
||||
Todos los procedimientos deben declarar:
|
||||
|
||||
```sql
|
||||
@CveUsuarioEjecuta int,
|
||||
@ErrorMostrar varchar(max) OUTPUT,
|
||||
@ErrorTipo int OUTPUT,
|
||||
@Return bit OUTPUT
|
||||
```
|
||||
|
||||
- `@CveUsuarioEjecuta` identifica al usuario autenticado que ejecuta la operación.
|
||||
- Al inicio, establecer `@ErrorMostrar = NULL`, `@ErrorTipo = 0` y `@Return = 0`.
|
||||
- Establecer `@Return = 1` únicamente cuando la operación termine correctamente.
|
||||
- `@ErrorTipo = 0`: sin error ni advertencia.
|
||||
- `@ErrorTipo = 1`: error.
|
||||
- `@ErrorTipo = 2`: advertencia.
|
||||
- `@ErrorMostrar` contiene el mensaje amigable en español que puede mostrarse al usuario.
|
||||
- No devolver contraseñas, hashes, cadenas de conexión, SQL, stack traces ni detalles internos mediante `@ErrorMostrar`.
|
||||
- Los procedimientos de consulta pueden devolver un result set, pero también deben poblar los parámetros comunes.
|
||||
- El repositorio debe convertir los parámetros de salida en un modelo de resultado como `DbResult<T>`.
|
||||
|
||||
## Control de errores
|
||||
|
||||
- Usar `SET NOCOUNT ON` y `TRY/CATCH` en todos los procedimientos.
|
||||
- Usar `SET XACT_ABORT ON` cuando exista una mutación o transacción.
|
||||
- Iniciar la transacción dentro de `TRY`, confirmar sólo después del éxito y ejecutar rollback en `CATCH` cuando `XACT_STATE() <> 0`.
|
||||
- Una validación fallida debe establecer los parámetros de salida y ejecutar `RETURN`; no debe continuar la mutación.
|
||||
- Para validar existencia, usar normalmente `EXISTS` en lugar de `COUNT(*)`.
|
||||
- En `CATCH`, generar un mensaje técnico separado con `ERROR_NUMBER()`, `ERROR_PROCEDURE()`, `ERROR_LINE()` y `ERROR_MESSAGE()` para el log.
|
||||
- El mensaje técnico puede notificarse con severidad informativa para que el ejecutor lo registre, pero nunca debe mostrarse directamente al usuario.
|
||||
|
||||
## Borrado lógico
|
||||
|
||||
- Está prohibido usar `DELETE` físico en procedimientos CRUD normales.
|
||||
- Para borrar: `Borrado = 1` y `FechaBorrado = SYSDATETIME()`.
|
||||
- Para restaurar: `Borrado = 0` y `FechaBorrado = NULL`.
|
||||
- Las consultas normales deben filtrar `Borrado = 0` en cada tabla participante, excepto cuando el caso de uso solicite expresamente registros borrados.
|
||||
- La eliminación permanente sólo puede realizarse mediante el procedimiento controlado de retención y limpieza.
|
||||
- Ninguna página ASPX puede llamar directamente a la limpieza física.
|
||||
|
||||
## Plantilla requerida
|
||||
|
||||
```sql
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PA_Sis_RolAdd]
|
||||
@Rol varchar(50),
|
||||
@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
|
||||
IF EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM dbo.Sis_Roles
|
||||
WHERE NombreRol = @Rol
|
||||
AND Borrado = 0
|
||||
)
|
||||
BEGIN
|
||||
SET @ErrorMostrar = CONCAT('El rol ''', @Rol, ''' ya se encuentra registrado.');
|
||||
SET @ErrorTipo = 2;
|
||||
RETURN;
|
||||
END;
|
||||
|
||||
INSERT INTO dbo.Sis_Roles
|
||||
(NombreRol, Activo, FechaCreacion)
|
||||
VALUES
|
||||
(@Rol, 1, SYSDATETIME());
|
||||
|
||||
SET @Return = 1;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
IF XACT_STATE() <> 0
|
||||
ROLLBACK TRANSACTION;
|
||||
|
||||
SET @ErrorMostrar = 'Ocurrió un error al agregar el rol; favor de contactar a Sistemas.';
|
||||
SET @ErrorTipo = 1;
|
||||
SET @Return = 0;
|
||||
|
||||
DECLARE @ErrorTecnico nvarchar(2048) = CONCAT(
|
||||
'Número: ', ERROR_NUMBER(),
|
||||
'; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Sis_RolAdd'),
|
||||
'; Línea: ', ERROR_LINE(),
|
||||
'; Detalle: ', ERROR_MESSAGE()
|
||||
);
|
||||
|
||||
RAISERROR(N'%s', 10, 1, @ErrorTecnico);
|
||||
END CATCH;
|
||||
END;
|
||||
GO
|
||||
```
|
||||
|
||||
Cuando el procedimiento use una transacción, debe incluir `BEGIN TRANSACTION` dentro de `TRY` y `COMMIT TRANSACTION` antes de establecer `@Return = 1`.
|
||||
102
AGENTS_WEBFORMS_PARTIAL_POSTBACKS.md
Normal file
102
AGENTS_WEBFORMS_PARTIAL_POSTBACKS.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Reglas obligatorias para formularios y postbacks parciales WebForms
|
||||
|
||||
Estas reglas complementan `AGENTS.md` y `AGENTS_PAGE_STRUCTURE.md`.
|
||||
|
||||
## Formulario principal
|
||||
|
||||
- `Site.master` es el único archivo autorizado para declarar el formulario principal:
|
||||
|
||||
```aspx
|
||||
<form id="form1" runat="server">
|
||||
```
|
||||
|
||||
- Ninguna página normal `.aspx` que use `Site.master` debe declarar `<form>`, `<form runat="server">`, `<html>`, `<head>` ni `<body>`.
|
||||
- No anidar formularios HTML dentro del formulario de `Site.master`.
|
||||
- Los formularios visuales de captura se construyen con `<div>`, controles WebForms y validadores dentro del placeholder `C`.
|
||||
- `Login.aspx` es la única excepción actual porque no usa `Site.master` y conserva su estructura HTML completa con su propio formulario de servidor.
|
||||
- No agregar una segunda excepción sin solicitud explícita.
|
||||
|
||||
## ScriptManager
|
||||
|
||||
- `Site.master` es el único dueño del `asp:ScriptManager` principal, actualmente `TSM`.
|
||||
- Las páginas de contenido no deben agregar otro `ScriptManager`.
|
||||
- Usar `asp:ScriptManagerProxy` solamente cuando una página requiera registrar servicios o scripts específicos que no pertenezcan globalmente al master.
|
||||
- El manejo global de `AsyncPostBackError` permanece en `Site.master`.
|
||||
|
||||
## UpdatePanel
|
||||
|
||||
- Usar `asp:UpdatePanel` cuando una interacción deba actualizar únicamente una sección sin refrescar visualmente toda la página.
|
||||
- No envolver todo el placeholder `C` en un único `UpdatePanel` por comodidad.
|
||||
- Crear paneles pequeños alrededor de la tabla, formulario, resumen o sección que realmente cambia.
|
||||
- Preferir esta configuración:
|
||||
|
||||
```aspx
|
||||
<asp:UpdatePanel ID="UpUsuarios" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="false">
|
||||
<ContentTemplate>
|
||||
<!-- Sección que se actualiza parcialmente -->
|
||||
</ContentTemplate>
|
||||
<Triggers>
|
||||
<asp:AsyncPostBackTrigger ControlID="BtnBuscar" EventName="Click" />
|
||||
</Triggers>
|
||||
</asp:UpdatePanel>
|
||||
```
|
||||
|
||||
- Usar `UpdateMode="Conditional"` de manera predeterminada.
|
||||
- Preferir `ChildrenAsTriggers="false"` y declarar triggers explícitos para que el comportamiento sea fácil de revisar.
|
||||
- Llamar `Update()` únicamente en paneles condicionales y sólo cuando el servidor haya modificado su contenido.
|
||||
- Evitar `UpdatePanel` anidados. Si son necesarios, documentar qué evento actualiza cada panel.
|
||||
- No actualizar varios paneles si sólo cambió una sección.
|
||||
- Los controles externos que disparen actualización parcial deben declararse como `AsyncPostBackTrigger` o registrarse mediante `ScriptManager.RegisterAsyncPostBackControl`.
|
||||
|
||||
## Operaciones que requieren postback completo
|
||||
|
||||
- Usar `PostBackTrigger` para cargas de archivos, descargas que escriben directamente en `Response`, exportaciones y otras operaciones incompatibles con postback asíncrono.
|
||||
- No intentar procesar `FileUpload` dentro de un postback parcial sin `PostBackTrigger`.
|
||||
- No iniciar una descarga binaria desde una respuesta parcial de `UpdatePanel`.
|
||||
|
||||
## Ciclo de vida y rendimiento
|
||||
|
||||
- Un postback parcial ejecuta el ciclo de vida de servidor de la página; no significa que el servidor procese únicamente el `UpdatePanel`.
|
||||
- Proteger cargas iniciales con `if (!IsPostBack)`.
|
||||
- No repetir consultas, catálogos o construcción del menú en cada evento si no son necesarios.
|
||||
- Toda consulta continúa ejecutándose mediante contratos, servicios, repositorios y procedimientos almacenados.
|
||||
- Preferir operaciones asíncronas de extremo a extremo para consultas lentas cuando la página esté configurada correctamente para async.
|
||||
- Configurar timeouts razonables y no dejar indicadores de carga activos indefinidamente.
|
||||
|
||||
## Experiencia de usuario
|
||||
|
||||
- Usar `asp:UpdateProgress` cuando la operación pueda tardar lo suficiente para que el usuario necesite retroalimentación.
|
||||
- Asociar el indicador al `UpdatePanel` correspondiente cuando sea posible.
|
||||
- Deshabilitar temporalmente botones que puedan generar envíos duplicados.
|
||||
- Mostrar mensajes amigables dentro de la sección actualizada o mediante el sistema global de mensajes.
|
||||
- Los detalles técnicos permanecen exclusivamente en el log.
|
||||
|
||||
## JavaScript después de un postback parcial
|
||||
|
||||
- Los scripts registrados globalmente no deben duplicarse dentro de un `UpdatePanel`.
|
||||
- Los componentes JavaScript que pierdan inicialización después de reemplazar HTML deben reinicializarse mediante `Sys.WebForms.PageRequestManager.endRequest`.
|
||||
- Evitar registrar múltiples veces el mismo handler en cada postback parcial.
|
||||
- Usar `ScriptManager.RegisterStartupScript` cuando un script de servidor deba ejecutarse después de una actualización parcial.
|
||||
|
||||
## Plantilla de página normal
|
||||
|
||||
```aspx
|
||||
<%@ Page Title="Página" Language="C#" MasterPageFile="~/Site.master"
|
||||
AutoEventWireup="true" CodeBehind="Pagina.aspx.cs"
|
||||
Inherits="SIG.Web.Pages.Modulo.Pagina" %>
|
||||
|
||||
<asp:Content ID="ContentHead" ContentPlaceHolderID="H" runat="server">
|
||||
</asp:Content>
|
||||
|
||||
<asp:Content ID="ContentMain" ContentPlaceHolderID="C" runat="server">
|
||||
<asp:UpdatePanel ID="UpContenido" runat="server"
|
||||
UpdateMode="Conditional" ChildrenAsTriggers="false">
|
||||
<ContentTemplate>
|
||||
<!-- No agregar otro form ni otro ScriptManager. -->
|
||||
</ContentTemplate>
|
||||
</asp:UpdatePanel>
|
||||
</asp:Content>
|
||||
|
||||
<asp:Content ID="ContentScripts" ContentPlaceHolderID="J" runat="server">
|
||||
</asp:Content>
|
||||
```
|
||||
2149
Esquema_BD_AguaHillo.sql
Normal file
2149
Esquema_BD_AguaHillo.sql
Normal file
File diff suppressed because it is too large
Load Diff
20226
Esquema_BD_AguaHillo_Idempotente.sql
Normal file
20226
Esquema_BD_AguaHillo_Idempotente.sql
Normal file
File diff suppressed because it is too large
Load Diff
4265
Esquema_BD_AguaHillo_v2.sql
Normal file
4265
Esquema_BD_AguaHillo_v2.sql
Normal file
File diff suppressed because it is too large
Load Diff
329
Esquema_Borrado_Logico_Limpieza.sql
Normal file
329
Esquema_Borrado_Logico_Limpieza.sql
Normal file
@@ -0,0 +1,329 @@
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
GO
|
||||
SET XACT_ABORT ON;
|
||||
GO
|
||||
|
||||
/*
|
||||
Borrado lógico y limpieza controlada.
|
||||
|
||||
Convención:
|
||||
Borrado = 0 y FechaBorrado IS NULL: registro vigente.
|
||||
Borrado = 1 y FechaBorrado IS NOT NULL: registro borrado lógicamente.
|
||||
|
||||
La limpieza física está deshabilitada por defecto. Cada tabla debe registrarse
|
||||
expresamente mediante dbo.Sis_ConfigurarLimpieza y la ejecución inicia siempre
|
||||
en modo simulación.
|
||||
*/
|
||||
|
||||
IF OBJECT_ID(N'dbo.Sis_PoliticasLimpieza', N'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE [dbo].[Sis_PoliticasLimpieza]
|
||||
(
|
||||
[Esquema] sysname NOT NULL,
|
||||
[Tabla] sysname NOT NULL,
|
||||
[DiasRetencion] int CONSTRAINT [DF_Sis_PoliticasLimpieza_DiasRetencion] DEFAULT ((365)) NOT NULL,
|
||||
[TamanoLote] int CONSTRAINT [DF_Sis_PoliticasLimpieza_TamanoLote] DEFAULT ((500)) NOT NULL,
|
||||
[Habilitada] bit CONSTRAINT [DF_Sis_PoliticasLimpieza_Habilitada] DEFAULT ((0)) NOT NULL,
|
||||
[FechaUltimaLimpieza] datetime2(0) NULL,
|
||||
[Borrado] bit CONSTRAINT [DF_Sis_PoliticasLimpieza_Borrado] DEFAULT ((0)) NOT NULL,
|
||||
[FechaBorrado] datetime2(0) NULL,
|
||||
CONSTRAINT [PK_Sis_PoliticasLimpieza] PRIMARY KEY CLUSTERED ([Esquema], [Tabla]),
|
||||
CONSTRAINT [CK_Sis_PoliticasLimpieza_DiasRetencion] CHECK ([DiasRetencion] BETWEEN (30) AND (3650)),
|
||||
CONSTRAINT [CK_Sis_PoliticasLimpieza_TamanoLote] CHECK ([TamanoLote] BETWEEN (1) AND (10000)),
|
||||
CONSTRAINT [CK_Sis_PoliticasLimpieza_Borrado] CHECK
|
||||
(
|
||||
([Borrado] = (0) AND [FechaBorrado] IS NULL)
|
||||
OR ([Borrado] = (1) AND [FechaBorrado] IS NOT NULL)
|
||||
)
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
/* Agrega la convención a todas las tablas de usuario existentes. */
|
||||
DECLARE @Esquema sysname;
|
||||
DECLARE @Tabla sysname;
|
||||
DECLARE @Objeto nvarchar(517);
|
||||
DECLARE @Sql nvarchar(max);
|
||||
DECLARE @Errores TABLE ([Esquema] sysname, [Tabla] sysname, [Mensaje] nvarchar(4000));
|
||||
|
||||
DECLARE TablasBorradoLogico CURSOR LOCAL FAST_FORWARD FOR
|
||||
SELECT S.[name], T.[name]
|
||||
FROM sys.tables AS T
|
||||
INNER JOIN sys.schemas AS S ON S.[schema_id] = T.[schema_id]
|
||||
WHERE T.[is_ms_shipped] = (0)
|
||||
AND T.[temporal_type] = (0)
|
||||
ORDER BY S.[name], T.[name];
|
||||
|
||||
OPEN TablasBorradoLogico;
|
||||
FETCH NEXT FROM TablasBorradoLogico INTO @Esquema, @Tabla;
|
||||
|
||||
WHILE @@FETCH_STATUS = (0)
|
||||
BEGIN
|
||||
SET @Objeto = QUOTENAME(@Esquema) + N'.' + QUOTENAME(@Tabla);
|
||||
|
||||
BEGIN TRY
|
||||
|
||||
IF COL_LENGTH(@Esquema + N'.' + @Tabla, N'Borrado') IS NULL
|
||||
BEGIN
|
||||
SET @Sql = N'ALTER TABLE ' + @Objeto
|
||||
+ N' ADD [Borrado] bit NOT NULL DEFAULT ((0)) WITH VALUES;';
|
||||
EXEC sys.sp_executesql @Sql;
|
||||
END;
|
||||
|
||||
IF COL_LENGTH(@Esquema + N'.' + @Tabla, N'FechaBorrado') IS NULL
|
||||
BEGIN
|
||||
SET @Sql = N'ALTER TABLE ' + @Objeto
|
||||
+ N' ADD [FechaBorrado] datetime2(0) NULL;';
|
||||
EXEC sys.sp_executesql @Sql;
|
||||
END;
|
||||
|
||||
IF NOT EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM sys.check_constraints AS C
|
||||
WHERE C.[parent_object_id] = OBJECT_ID(@Esquema + N'.' + @Tabla)
|
||||
AND C.[name] = N'CK_' + @Tabla + N'_BorradoLogico'
|
||||
)
|
||||
AND LEN(N'CK_' + @Tabla + N'_BorradoLogico') <= 128
|
||||
BEGIN
|
||||
SET @Sql = N'ALTER TABLE ' + @Objeto
|
||||
+ 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;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
INSERT INTO @Errores ([Esquema], [Tabla], [Mensaje])
|
||||
VALUES (@Esquema, @Tabla, ERROR_MESSAGE());
|
||||
END CATCH;
|
||||
|
||||
FETCH NEXT FROM TablasBorradoLogico INTO @Esquema, @Tabla;
|
||||
END;
|
||||
|
||||
CLOSE TablasBorradoLogico;
|
||||
DEALLOCATE TablasBorradoLogico;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM @Errores)
|
||||
SELECT [Esquema], [Tabla], [Mensaje] FROM @Errores ORDER BY [Esquema], [Tabla];
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Sis_ConfigurarLimpieza]
|
||||
@Esquema sysname = N'dbo',
|
||||
@Tabla sysname,
|
||||
@DiasRetencion int = 365,
|
||||
@TamanoLote int = 500,
|
||||
@Habilitada bit = 0
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
IF @DiasRetencion NOT BETWEEN 30 AND 3650
|
||||
THROW 51000, N'Los días de retención deben estar entre 30 y 3650.', 1;
|
||||
|
||||
IF @TamanoLote NOT BETWEEN 1 AND 10000
|
||||
THROW 51001, N'El tamaño de lote debe estar entre 1 y 10000.', 1;
|
||||
|
||||
IF @Esquema = N'dbo' AND @Tabla = N'Sis_PoliticasLimpieza'
|
||||
THROW 51002, N'No se permite configurar la limpieza de la tabla de políticas.', 1;
|
||||
|
||||
IF OBJECT_ID(QUOTENAME(@Esquema) + N'.' + QUOTENAME(@Tabla), N'U') IS NULL
|
||||
THROW 51003, N'La tabla indicada no existe.', 1;
|
||||
|
||||
IF COL_LENGTH(@Esquema + N'.' + @Tabla, N'Borrado') IS NULL
|
||||
OR COL_LENGTH(@Esquema + N'.' + @Tabla, N'FechaBorrado') IS NULL
|
||||
THROW 51004, N'La tabla no tiene las columnas de borrado lógico.', 1;
|
||||
|
||||
UPDATE [dbo].[Sis_PoliticasLimpieza]
|
||||
SET [DiasRetencion] = @DiasRetencion,
|
||||
[TamanoLote] = @TamanoLote,
|
||||
[Habilitada] = @Habilitada,
|
||||
[Borrado] = (0),
|
||||
[FechaBorrado] = NULL
|
||||
WHERE [Esquema] = @Esquema
|
||||
AND [Tabla] = @Tabla;
|
||||
|
||||
IF @@ROWCOUNT = (0)
|
||||
BEGIN
|
||||
INSERT INTO [dbo].[Sis_PoliticasLimpieza]
|
||||
([Esquema], [Tabla], [DiasRetencion], [TamanoLote], [Habilitada])
|
||||
VALUES
|
||||
(@Esquema, @Tabla, @DiasRetencion, @TamanoLote, @Habilitada);
|
||||
END;
|
||||
|
||||
DECLARE @NombreIndice sysname = N'IX_BorradoLogico_FechaBorrado';
|
||||
DECLARE @SqlIndice nvarchar(max);
|
||||
|
||||
IF NOT EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM sys.indexes
|
||||
WHERE [object_id] = OBJECT_ID(QUOTENAME(@Esquema) + N'.' + QUOTENAME(@Tabla))
|
||||
AND [name] = @NombreIndice
|
||||
)
|
||||
BEGIN
|
||||
SET @SqlIndice = N'CREATE NONCLUSTERED INDEX ' + QUOTENAME(@NombreIndice)
|
||||
+ N' ON ' + QUOTENAME(@Esquema) + N'.' + QUOTENAME(@Tabla)
|
||||
+ N' ([FechaBorrado]) WHERE [Borrado] = (1);';
|
||||
EXEC sys.sp_executesql @SqlIndice;
|
||||
END;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Sis_LimpiarRegistrosBorrados]
|
||||
@SoloSimular bit = 1,
|
||||
@ConfirmarBorradoFisico bit = 0,
|
||||
@MaximoLotesPorTabla int = 100
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
IF @MaximoLotesPorTabla NOT BETWEEN 1 AND 10000
|
||||
THROW 51010, N'El máximo de lotes debe estar entre 1 y 10000.', 1;
|
||||
|
||||
IF @SoloSimular = (0) AND @ConfirmarBorradoFisico = (0)
|
||||
THROW 51011, N'Para borrar físicamente debe enviar @ConfirmarBorradoFisico = 1.', 1;
|
||||
|
||||
DECLARE @Resultados TABLE
|
||||
(
|
||||
[Esquema] sysname NOT NULL,
|
||||
[Tabla] sysname NOT NULL,
|
||||
[FilasElegibles] bigint NOT NULL,
|
||||
[FilasEliminadas] bigint NOT NULL,
|
||||
[Resultado] nvarchar(4000) NOT NULL
|
||||
);
|
||||
|
||||
DECLARE @Esquema sysname;
|
||||
DECLARE @Tabla sysname;
|
||||
DECLARE @DiasRetencion int;
|
||||
DECLARE @TamanoLote int;
|
||||
DECLARE @Objeto nvarchar(517);
|
||||
DECLARE @Sql nvarchar(max);
|
||||
DECLARE @Errores TABLE ([Esquema] sysname, [Tabla] sysname, [Mensaje] nvarchar(4000));
|
||||
DECLARE @Elegibles bigint;
|
||||
DECLARE @Eliminadas bigint;
|
||||
DECLARE @FilasLote int;
|
||||
DECLARE @NumeroLote int;
|
||||
|
||||
DECLARE Politicas CURSOR LOCAL FAST_FORWARD FOR
|
||||
SELECT [Esquema], [Tabla], [DiasRetencion], [TamanoLote]
|
||||
FROM [dbo].[Sis_PoliticasLimpieza]
|
||||
WHERE [Habilitada] = (1)
|
||||
AND [Borrado] = (0)
|
||||
ORDER BY [Esquema], [Tabla];
|
||||
|
||||
OPEN Politicas;
|
||||
FETCH NEXT FROM Politicas INTO @Esquema, @Tabla, @DiasRetencion, @TamanoLote;
|
||||
|
||||
WHILE @@FETCH_STATUS = (0)
|
||||
BEGIN
|
||||
SET @Objeto = QUOTENAME(@Esquema) + N'.' + QUOTENAME(@Tabla);
|
||||
|
||||
BEGIN TRY
|
||||
SET @Elegibles = (0);
|
||||
SET @Eliminadas = (0);
|
||||
|
||||
BEGIN TRY
|
||||
IF OBJECT_ID(@Objeto, N'U') IS NULL
|
||||
THROW 51012, N'La tabla configurada ya no existe.', 1;
|
||||
|
||||
SET @Sql = N'SELECT @Cantidad = COUNT_BIG(*) FROM ' + @Objeto
|
||||
+ N' WHERE [Borrado] = (1)'
|
||||
+ N' AND [FechaBorrado] < DATEADD(DAY, -@Dias, SYSDATETIME());';
|
||||
|
||||
EXEC sys.sp_executesql
|
||||
@Sql,
|
||||
N'@Dias int, @Cantidad bigint OUTPUT',
|
||||
@Dias = @DiasRetencion,
|
||||
@Cantidad = @Elegibles OUTPUT;
|
||||
|
||||
IF @SoloSimular = (1)
|
||||
BEGIN
|
||||
INSERT INTO @Resultados
|
||||
([Esquema], [Tabla], [FilasElegibles], [FilasEliminadas], [Resultado])
|
||||
VALUES
|
||||
(@Esquema, @Tabla, @Elegibles, (0), N'Simulación; no se eliminó información.');
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
SET @NumeroLote = (0);
|
||||
SET @FilasLote = (1);
|
||||
|
||||
WHILE @FilasLote > (0) AND @NumeroLote < @MaximoLotesPorTabla
|
||||
BEGIN
|
||||
SET @Sql = N'DELETE TOP (@Lote) FROM ' + @Objeto
|
||||
+ N' WHERE [Borrado] = (1)'
|
||||
+ N' AND [FechaBorrado] < DATEADD(DAY, -@Dias, SYSDATETIME());'
|
||||
+ N' SET @Filas = @@ROWCOUNT;';
|
||||
|
||||
EXEC sys.sp_executesql
|
||||
@Sql,
|
||||
N'@Lote int, @Dias int, @Filas int OUTPUT',
|
||||
@Lote = @TamanoLote,
|
||||
@Dias = @DiasRetencion,
|
||||
@Filas = @FilasLote OUTPUT;
|
||||
|
||||
SET @Eliminadas += @FilasLote;
|
||||
SET @NumeroLote += (1);
|
||||
END;
|
||||
|
||||
UPDATE [dbo].[Sis_PoliticasLimpieza]
|
||||
SET [FechaUltimaLimpieza] = SYSDATETIME()
|
||||
WHERE [Esquema] = @Esquema
|
||||
AND [Tabla] = @Tabla;
|
||||
|
||||
INSERT INTO @Resultados
|
||||
([Esquema], [Tabla], [FilasElegibles], [FilasEliminadas], [Resultado])
|
||||
VALUES
|
||||
(@Esquema, @Tabla, @Elegibles, @Eliminadas, N'Limpieza física ejecutada.');
|
||||
END;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
INSERT INTO @Resultados
|
||||
([Esquema], [Tabla], [FilasElegibles], [FilasEliminadas], [Resultado])
|
||||
VALUES
|
||||
(@Esquema, @Tabla, @Elegibles, @Eliminadas,
|
||||
CONCAT(N'No se eliminó la tabla: ', ERROR_MESSAGE()));
|
||||
END CATCH;
|
||||
|
||||
FETCH NEXT FROM Politicas INTO @Esquema, @Tabla, @DiasRetencion, @TamanoLote;
|
||||
END;
|
||||
|
||||
CLOSE Politicas;
|
||||
DEALLOCATE Politicas;
|
||||
|
||||
SELECT [Esquema], [Tabla], [FilasElegibles], [FilasEliminadas], [Resultado]
|
||||
FROM @Resultados
|
||||
ORDER BY [Esquema], [Tabla];
|
||||
END;
|
||||
GO
|
||||
|
||||
/*
|
||||
Ejemplos (no se ejecutan automáticamente):
|
||||
|
||||
-- Borrado lógico desde un repositorio/servicio:
|
||||
UPDATE dbo.Fraccionamientos
|
||||
SET Borrado = 1, FechaBorrado = SYSDATETIME()
|
||||
WHERE CveFraccionamiento = @CveFraccionamiento;
|
||||
|
||||
-- Restauración:
|
||||
UPDATE dbo.Fraccionamientos
|
||||
SET Borrado = 0, FechaBorrado = NULL
|
||||
WHERE CveFraccionamiento = @CveFraccionamiento;
|
||||
|
||||
-- Habilitar limpieza de una tabla después de 365 días:
|
||||
EXEC dbo.Sis_ConfigurarLimpieza
|
||||
@Esquema = N'dbo', @Tabla = N'Fraccionamientos',
|
||||
@DiasRetencion = 365, @TamanoLote = 500, @Habilitada = 1;
|
||||
|
||||
-- Revisar qué se eliminaría (modo predeterminado y seguro):
|
||||
EXEC dbo.Sis_LimpiarRegistrosBorrados;
|
||||
|
||||
-- Ejecutar la limpieza física confirmada:
|
||||
EXEC dbo.Sis_LimpiarRegistrosBorrados
|
||||
@SoloSimular = 0, @ConfirmarBorradoFisico = 1;
|
||||
*/
|
||||
611
Esquema_Tramites_Pagos_AguaHillo.sql
Normal file
611
Esquema_Tramites_Pagos_AguaHillo.sql
Normal file
@@ -0,0 +1,611 @@
|
||||
/*
|
||||
Módulo de fraccionamientos, trámites, ventas y pagos.
|
||||
|
||||
Este archivo se ejecuta después de Esquema_BD_AguaHillo.sql.
|
||||
|
||||
Nombres reemplazados:
|
||||
Des_Desarrolladoras -> Clientes
|
||||
Des_Desarrollos -> Fraccionamientos
|
||||
Des_EtapasDesarrollo -> FraccionamientosEtapas
|
||||
Des_Lotes -> FraccionamientosLotes
|
||||
Sis_Users -> Sis_Usuarios
|
||||
|
||||
Regla del fondo inicial:
|
||||
- Se registra una Venta con TipoDocto = 'Ant'.
|
||||
- La venta usa un producto genérico de ProductosServicios, por ejemplo "Fondo inicial".
|
||||
- El detalle del fondo no se relaciona con una solicitud específica.
|
||||
- Sólo el dinero efectivamente registrado en Vta_Pagos queda disponible.
|
||||
- Vta_AplicacionesSaldoFavor distribuye ese dinero entre trámites posteriores.
|
||||
- Ventas, pagos y aplicaciones pueden generar su propia Conta_PrePoliza.
|
||||
*/
|
||||
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
GO
|
||||
SET XACT_ABORT ON;
|
||||
GO
|
||||
|
||||
/* ==================== FRACCIONAMIENTOS ==================== */
|
||||
|
||||
CREATE TABLE [dbo].[Fraccionamientos] (
|
||||
[CveFraccionamiento] int IDENTITY(1,1) NOT NULL,
|
||||
[CveCliente] int NOT NULL,
|
||||
[Fraccionamiento] varchar(200) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[Hectareas] decimal(18,4) NULL,
|
||||
[Ubicacion] varchar(500) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[Estatus] varchar(50) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_Fraccionamientos_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_Fraccionamientos_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
[FechaModificacion] datetime NULL,
|
||||
[CveUsuario] int NOT NULL,
|
||||
CONSTRAINT [PK_Fraccionamientos] PRIMARY KEY CLUSTERED ([CveFraccionamiento]),
|
||||
CONSTRAINT [UQ_Fraccionamientos_Cliente_Nombre] UNIQUE NONCLUSTERED ([CveCliente], [Fraccionamiento]),
|
||||
CONSTRAINT [UQ_Fraccionamientos_Cliente] UNIQUE NONCLUSTERED ([CveFraccionamiento], [CveCliente]),
|
||||
CONSTRAINT [CK_Fraccionamientos_Hectareas] CHECK ([Hectareas] IS NULL OR [Hectareas] >= (0))
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[FraccionamientosEtapas] (
|
||||
[CveFraccionamientoEtapa] int IDENTITY(1,1) NOT NULL,
|
||||
[CveFraccionamiento] int NOT NULL,
|
||||
[Etapa] varchar(150) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[Hectareas] decimal(18,4) NULL,
|
||||
[Orden] int CONSTRAINT [DF_FraccionamientosEtapas_Orden] DEFAULT ((0)) NOT NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_FraccionamientosEtapas_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_FraccionamientosEtapas_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
[FechaModificacion] datetime NULL,
|
||||
[CveUsuario] int NOT NULL,
|
||||
CONSTRAINT [PK_FraccionamientosEtapas] PRIMARY KEY CLUSTERED ([CveFraccionamientoEtapa]),
|
||||
CONSTRAINT [UQ_FraccionamientosEtapas_Fraccionamiento_Etapa] UNIQUE NONCLUSTERED ([CveFraccionamiento], [Etapa]),
|
||||
CONSTRAINT [UQ_FraccionamientosEtapas_Fraccionamiento] UNIQUE NONCLUSTERED ([CveFraccionamientoEtapa], [CveFraccionamiento]),
|
||||
CONSTRAINT [CK_FraccionamientosEtapas_Hectareas] CHECK ([Hectareas] IS NULL OR [Hectareas] >= (0))
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[FraccionamientosLotes] (
|
||||
[CveFraccionamientoLote] int IDENTITY(1,1) NOT NULL,
|
||||
[CveFraccionamientoEtapa] int NOT NULL,
|
||||
[Tipo] varchar(100) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[Calle] varchar(200) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[Catastro] varchar(100) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[MetrosCuadrados] decimal(18,4) NULL,
|
||||
[Numero] varchar(50) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[Duplicador] varchar(50) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_FraccionamientosLotes_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_FraccionamientosLotes_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
[FechaModificacion] datetime NULL,
|
||||
[CveUsuario] int NOT NULL,
|
||||
CONSTRAINT [PK_FraccionamientosLotes] PRIMARY KEY CLUSTERED ([CveFraccionamientoLote]),
|
||||
CONSTRAINT [CK_FraccionamientosLotes_MetrosCuadrados] CHECK ([MetrosCuadrados] IS NULL OR [MetrosCuadrados] >= (0))
|
||||
);
|
||||
GO
|
||||
|
||||
/* ==================== CATÁLOGOS DE TRÁMITES ==================== */
|
||||
|
||||
CREATE TABLE [dbo].[Tra_TiposTramite] (
|
||||
[CveTipoTramite] int IDENTITY(1,1) NOT NULL,
|
||||
[Codigo] varchar(50) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[TipoTramite] varchar(200) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[Descripcion] varchar(500) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[Orden] int CONSTRAINT [DF_Tra_TiposTramite_Orden] DEFAULT ((0)) NOT NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_Tra_TiposTramite_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_Tra_TiposTramite_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
[FechaModificacion] datetime NULL,
|
||||
[CveUsuario] int NOT NULL,
|
||||
CONSTRAINT [PK_Tra_TiposTramite] PRIMARY KEY CLUSTERED ([CveTipoTramite]),
|
||||
CONSTRAINT [UQ_Tra_TiposTramite_Codigo] UNIQUE NONCLUSTERED ([Codigo])
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Tra_EstatusSolicitud] (
|
||||
[CveEstatusSolicitud] int IDENTITY(1,1) NOT NULL,
|
||||
[Codigo] varchar(50) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[EstatusSolicitud] varchar(100) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[Orden] int CONSTRAINT [DF_Tra_EstatusSolicitud_Orden] DEFAULT ((0)) NOT NULL,
|
||||
[EsFinal] bit CONSTRAINT [DF_Tra_EstatusSolicitud_EsFinal] DEFAULT ((0)) NOT NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_Tra_EstatusSolicitud_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
CONSTRAINT [PK_Tra_EstatusSolicitud] PRIMARY KEY CLUSTERED ([CveEstatusSolicitud]),
|
||||
CONSTRAINT [UQ_Tra_EstatusSolicitud_Codigo] UNIQUE NONCLUSTERED ([Codigo])
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Tra_TiposDocumento] (
|
||||
[CveTipoDocumento] int IDENTITY(1,1) NOT NULL,
|
||||
[TipoDocumento] varchar(200) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[Descripcion] varchar(500) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[ExtensionesPermitidas] varchar(200) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[TamanoMaximoMb] int NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_Tra_TiposDocumento_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_Tra_TiposDocumento_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
[FechaModificacion] datetime NULL,
|
||||
[CveUsuario] int NOT NULL,
|
||||
CONSTRAINT [PK_Tra_TiposDocumento] PRIMARY KEY CLUSTERED ([CveTipoDocumento]),
|
||||
CONSTRAINT [UQ_Tra_TiposDocumento_Nombre] UNIQUE NONCLUSTERED ([TipoDocumento]),
|
||||
CONSTRAINT [CK_Tra_TiposDocumento_Tamano] CHECK ([TamanoMaximoMb] IS NULL OR [TamanoMaximoMb] > (0))
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Tra_TiposPregunta] (
|
||||
[CveTipoPregunta] int IDENTITY(1,1) NOT NULL,
|
||||
[Codigo] varchar(50) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[TipoPregunta] varchar(100) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_Tra_TiposPregunta_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
CONSTRAINT [PK_Tra_TiposPregunta] PRIMARY KEY CLUSTERED ([CveTipoPregunta]),
|
||||
CONSTRAINT [UQ_Tra_TiposPregunta_Codigo] UNIQUE NONCLUSTERED ([Codigo])
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Tra_Preguntas] (
|
||||
[CvePregunta] int IDENTITY(1,1) NOT NULL,
|
||||
[CveTipoPregunta] int NOT NULL,
|
||||
[Pregunta] varchar(500) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[Descripcion] varchar(1000) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[EsRequerida] bit CONSTRAINT [DF_Tra_Preguntas_EsRequerida] DEFAULT ((0)) NOT NULL,
|
||||
[Orden] int CONSTRAINT [DF_Tra_Preguntas_Orden] DEFAULT ((0)) NOT NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_Tra_Preguntas_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_Tra_Preguntas_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
[FechaModificacion] datetime NULL,
|
||||
[CveUsuario] int NOT NULL,
|
||||
CONSTRAINT [PK_Tra_Preguntas] PRIMARY KEY CLUSTERED ([CvePregunta])
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Tra_OpcionesPregunta] (
|
||||
[CveOpcionPregunta] int IDENTITY(1,1) NOT NULL,
|
||||
[CvePregunta] int NOT NULL,
|
||||
[TextoOpcion] varchar(300) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[ValorOpcion] varchar(300) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[Orden] int CONSTRAINT [DF_Tra_OpcionesPregunta_Orden] DEFAULT ((0)) NOT NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_Tra_OpcionesPregunta_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
CONSTRAINT [PK_Tra_OpcionesPregunta] PRIMARY KEY CLUSTERED ([CveOpcionPregunta]),
|
||||
CONSTRAINT [UQ_Tra_OpcionesPregunta_Pregunta_Texto] UNIQUE NONCLUSTERED ([CvePregunta], [TextoOpcion])
|
||||
);
|
||||
GO
|
||||
|
||||
/* ==================== SOLICITUDES Y FLUJO ==================== */
|
||||
|
||||
CREATE TABLE [dbo].[Tra_Solicitudes] (
|
||||
[CveSolicitud] int IDENTITY(1,1) NOT NULL,
|
||||
[Folio] varchar(50) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[CveCliente] int NOT NULL,
|
||||
[CveFraccionamiento] int NOT NULL,
|
||||
[CveFraccionamientoEtapa] int NOT NULL,
|
||||
[CveTipoTramite] int NOT NULL,
|
||||
[CveEstatusSolicitud] int NOT NULL,
|
||||
[CveUsuarioSolicita] int NULL,
|
||||
[FechaInicio] datetime NULL,
|
||||
[FechaEnvio] datetime NULL,
|
||||
[FechaAutorizacion] datetime NULL,
|
||||
[FechaRechazo] datetime NULL,
|
||||
[ComentariosCliente] varchar(max) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[ComentariosAdministrativos] varchar(max) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_Tra_Solicitudes_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
[FechaModificacion] datetime NULL,
|
||||
CONSTRAINT [PK_Tra_Solicitudes] PRIMARY KEY CLUSTERED ([CveSolicitud]),
|
||||
CONSTRAINT [UQ_Tra_Solicitudes_Folio] UNIQUE NONCLUSTERED ([Folio]),
|
||||
CONSTRAINT [UQ_Tra_Solicitudes_Etapa_TipoTramite] UNIQUE NONCLUSTERED ([CveFraccionamientoEtapa], [CveTipoTramite])
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Tra_SolicitudesDependencias] (
|
||||
[CveSolicitudDependencia] int IDENTITY(1,1) NOT NULL,
|
||||
[CveSolicitud] int NOT NULL,
|
||||
[CveSolicitudRequerida] int NOT NULL,
|
||||
[EsObligatoria] bit CONSTRAINT [DF_Tra_SolicitudesDependencias_EsObligatoria] DEFAULT ((1)) NOT NULL,
|
||||
[Condicion] varchar(500) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_Tra_SolicitudesDependencias_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
[CveUsuario] int NOT NULL,
|
||||
CONSTRAINT [PK_Tra_SolicitudesDependencias] PRIMARY KEY CLUSTERED ([CveSolicitudDependencia]),
|
||||
CONSTRAINT [UQ_Tra_SolicitudesDependencias] UNIQUE NONCLUSTERED ([CveSolicitud], [CveSolicitudRequerida]),
|
||||
CONSTRAINT [CK_Tra_SolicitudesDependencias_Diferentes] CHECK ([CveSolicitud] <> [CveSolicitudRequerida])
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Tra_HistorialEstatusSolicitud] (
|
||||
[CveHistorialEstatusSolicitud] int IDENTITY(1,1) NOT NULL,
|
||||
[CveSolicitud] int NOT NULL,
|
||||
[CveEstatusSolicitud] int NOT NULL,
|
||||
[CveUsuario] int NULL,
|
||||
[Comentarios] varchar(1000) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_Tra_HistorialEstatusSolicitud_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
CONSTRAINT [PK_Tra_HistorialEstatusSolicitud] PRIMARY KEY CLUSTERED ([CveHistorialEstatusSolicitud])
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Tra_DocumentosRequeridos] (
|
||||
[CveDocumentoRequerido] int IDENTITY(1,1) NOT NULL,
|
||||
[CveTipoTramite] int NOT NULL,
|
||||
[CveTipoDocumento] int NOT NULL,
|
||||
[EsRequerido] bit CONSTRAINT [DF_Tra_DocumentosRequeridos_EsRequerido] DEFAULT ((1)) NOT NULL,
|
||||
[Orden] int CONSTRAINT [DF_Tra_DocumentosRequeridos_Orden] DEFAULT ((0)) NOT NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_Tra_DocumentosRequeridos_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
CONSTRAINT [PK_Tra_DocumentosRequeridos] PRIMARY KEY CLUSTERED ([CveDocumentoRequerido]),
|
||||
CONSTRAINT [UQ_Tra_DocumentosRequeridos] UNIQUE NONCLUSTERED ([CveTipoTramite], [CveTipoDocumento])
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Tra_DocumentosSolicitud] (
|
||||
[CveDocumentoSolicitud] int IDENTITY(1,1) NOT NULL,
|
||||
[CveSolicitud] int NOT NULL,
|
||||
[CveTipoDocumento] int NOT NULL,
|
||||
[NombreArchivo] varchar(255) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[NombreArchivoFisico] varchar(255) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[RutaArchivo] varchar(1000) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[ContentType] varchar(100) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[TamanoBytes] bigint NULL,
|
||||
[ComentarioCliente] varchar(1000) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[ComentarioAdministrativo] varchar(1000) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[EsAprobado] bit NULL,
|
||||
[CveUsuarioCarga] int NULL,
|
||||
[CveUsuarioRevisa] int NULL,
|
||||
[FechaRevision] datetime NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_Tra_DocumentosSolicitud_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
CONSTRAINT [PK_Tra_DocumentosSolicitud] PRIMARY KEY CLUSTERED ([CveDocumentoSolicitud]),
|
||||
CONSTRAINT [CK_Tra_DocumentosSolicitud_Tamano] CHECK ([TamanoBytes] IS NULL OR [TamanoBytes] >= (0))
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Tra_PreguntasTramite] (
|
||||
[CvePreguntaTramite] int IDENTITY(1,1) NOT NULL,
|
||||
[CveTipoTramite] int NOT NULL,
|
||||
[CvePregunta] int NOT NULL,
|
||||
[EsRequerida] bit CONSTRAINT [DF_Tra_PreguntasTramite_EsRequerida] DEFAULT ((0)) NOT NULL,
|
||||
[Orden] int CONSTRAINT [DF_Tra_PreguntasTramite_Orden] DEFAULT ((0)) NOT NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_Tra_PreguntasTramite_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
CONSTRAINT [PK_Tra_PreguntasTramite] PRIMARY KEY CLUSTERED ([CvePreguntaTramite]),
|
||||
CONSTRAINT [UQ_Tra_PreguntasTramite] UNIQUE NONCLUSTERED ([CveTipoTramite], [CvePregunta])
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Tra_RespuestasSolicitud] (
|
||||
[CveRespuestaSolicitud] int IDENTITY(1,1) NOT NULL,
|
||||
[CveSolicitud] int NOT NULL,
|
||||
[CvePregunta] int NOT NULL,
|
||||
[CveOpcionPregunta] int NULL,
|
||||
[Valor] varchar(max) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_Tra_RespuestasSolicitud_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
[FechaModificacion] datetime NULL,
|
||||
[CveUsuario] int NULL,
|
||||
CONSTRAINT [PK_Tra_RespuestasSolicitud] PRIMARY KEY CLUSTERED ([CveRespuestaSolicitud]),
|
||||
CONSTRAINT [CK_Tra_RespuestasSolicitud_Valor] CHECK ([CveOpcionPregunta] IS NOT NULL OR [Valor] IS NOT NULL)
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Tra_CostosTramite] (
|
||||
[CveCostoTramite] int IDENTITY(1,1) NOT NULL,
|
||||
[CveTipoTramite] int NOT NULL,
|
||||
[CvePS] int NOT NULL,
|
||||
[CveMoneda] int NOT NULL,
|
||||
[Descripcion] varchar(300) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[Importe] decimal(18,2) CONSTRAINT [DF_Tra_CostosTramite_Importe] DEFAULT ((0)) NOT NULL,
|
||||
[FechaInicioVigencia] date NULL,
|
||||
[FechaFinVigencia] date NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_Tra_CostosTramite_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_Tra_CostosTramite_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
[FechaModificacion] datetime NULL,
|
||||
[CveUsuario] int NOT NULL,
|
||||
CONSTRAINT [PK_Tra_CostosTramite] PRIMARY KEY CLUSTERED ([CveCostoTramite]),
|
||||
CONSTRAINT [UQ_Tra_CostosTramite_Tipo_Producto_Inicio] UNIQUE NONCLUSTERED ([CveTipoTramite], [CvePS], [FechaInicioVigencia]),
|
||||
CONSTRAINT [CK_Tra_CostosTramite_Importe] CHECK ([Importe] >= (0)),
|
||||
CONSTRAINT [CK_Tra_CostosTramite_Vigencia] CHECK ([FechaFinVigencia] IS NULL OR [FechaInicioVigencia] IS NULL OR [FechaFinVigencia] >= [FechaInicioVigencia])
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Tra_NotasSolicitud] (
|
||||
[CveNotaSolicitud] int IDENTITY(1,1) NOT NULL,
|
||||
[CveSolicitud] int NOT NULL,
|
||||
[CveUsuario] int NULL,
|
||||
[TipoNota] varchar(50) COLLATE Modern_Spanish_CS_AI CONSTRAINT [DF_Tra_NotasSolicitud_TipoNota] DEFAULT ('General') NOT NULL,
|
||||
[Comentario] varchar(max) COLLATE Modern_Spanish_CS_AI NOT NULL,
|
||||
[EsVisibleParaCliente] bit CONSTRAINT [DF_Tra_NotasSolicitud_Visible] DEFAULT ((1)) NOT NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_Tra_NotasSolicitud_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
CONSTRAINT [PK_Tra_NotasSolicitud] PRIMARY KEY CLUSTERED ([CveNotaSolicitud])
|
||||
);
|
||||
GO
|
||||
|
||||
/* ==================== VENTAS, PAGOS Y SALDO A FAVOR ==================== */
|
||||
|
||||
CREATE TABLE [dbo].[VentasDetalle] (
|
||||
[CveVentaDetalle] int IDENTITY(1,1) NOT NULL,
|
||||
[CveVenta] int NOT NULL,
|
||||
[CvePS] int NOT NULL,
|
||||
[CveSolicitud] int NULL,
|
||||
[CveCostoTramite] int NULL,
|
||||
[Cantidad] decimal(18,4) CONSTRAINT [DF_VentasDetalle_Cantidad] DEFAULT ((1)) NOT NULL,
|
||||
[PrecioUnitario] decimal(18,2) CONSTRAINT [DF_VentasDetalle_PrecioUnitario] DEFAULT ((0)) NOT NULL,
|
||||
[Descuento] decimal(18,2) CONSTRAINT [DF_VentasDetalle_Descuento] DEFAULT ((0)) NOT NULL,
|
||||
[IVA] decimal(18,2) CONSTRAINT [DF_VentasDetalle_IVA] DEFAULT ((0)) NOT NULL,
|
||||
[Importe] decimal(18,2) CONSTRAINT [DF_VentasDetalle_Importe] DEFAULT ((0)) NOT NULL,
|
||||
[EsFondoInicial] bit CONSTRAINT [DF_VentasDetalle_EsFondoInicial] DEFAULT ((0)) NOT NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_VentasDetalle_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
[CveUsuario] int NOT NULL,
|
||||
CONSTRAINT [PK_VentasDetalle] PRIMARY KEY CLUSTERED ([CveVentaDetalle]),
|
||||
CONSTRAINT [CK_VentasDetalle_Importes] CHECK ([Cantidad] > (0) AND [PrecioUnitario] >= (0) AND [Descuento] >= (0) AND [IVA] >= (0) AND [Importe] >= (0)),
|
||||
CONSTRAINT [CK_VentasDetalle_FondoInicial] CHECK ([EsFondoInicial] = (0) OR ([CveSolicitud] IS NULL AND [CveCostoTramite] IS NULL))
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Vta_Pagos] (
|
||||
[CvePago] int IDENTITY(1,1) NOT NULL,
|
||||
[CveCliente] int NOT NULL,
|
||||
[CveVenta] int NOT NULL,
|
||||
[CveSolicitud] int NULL,
|
||||
[CveFormaPago] tinyint NOT NULL,
|
||||
[CveMoneda] int NOT NULL,
|
||||
[CvePrePoliza] int NULL,
|
||||
[FechaPago] datetime NOT NULL,
|
||||
[Importe] decimal(18,2) NOT NULL,
|
||||
[Referencia] varchar(200) COLLATE Modern_Spanish_CS_AI NULL,
|
||||
[EsSaldoFavor] bit CONSTRAINT [DF_Vta_Pagos_EsSaldoFavor] DEFAULT ((0)) NOT NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_Vta_Pagos_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
[FechaAlta] datetime CONSTRAINT [DF_Vta_Pagos_FechaAlta] DEFAULT (GETDATE()) NOT NULL,
|
||||
[CveUsuario] int NOT NULL,
|
||||
CONSTRAINT [PK_Vta_Pagos] PRIMARY KEY CLUSTERED ([CvePago]),
|
||||
CONSTRAINT [CK_Vta_Pagos_Importe] CHECK ([Importe] > (0)),
|
||||
CONSTRAINT [CK_Vta_Pagos_SaldoFavor] CHECK ([EsSaldoFavor] = (0) OR [CveSolicitud] IS NULL)
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Vta_AplicacionesSaldoFavor] (
|
||||
[CveAplicacionSaldoFavor] int IDENTITY(1,1) NOT NULL,
|
||||
[CvePagoOrigen] int NOT NULL,
|
||||
[CveVentaDestino] int NOT NULL,
|
||||
[CveVentaDetalleDestino] int NULL,
|
||||
[CveSolicitud] int NOT NULL,
|
||||
[CvePrePoliza] int NULL,
|
||||
[Importe] decimal(18,2) NOT NULL,
|
||||
[FechaAplicacion] datetime CONSTRAINT [DF_Vta_AplicacionesSaldoFavor_Fecha] DEFAULT (GETDATE()) NOT NULL,
|
||||
[EsActivo] bit CONSTRAINT [DF_Vta_AplicacionesSaldoFavor_EsActivo] DEFAULT ((1)) NOT NULL,
|
||||
[CveUsuario] int NOT NULL,
|
||||
CONSTRAINT [PK_Vta_AplicacionesSaldoFavor] PRIMARY KEY CLUSTERED ([CveAplicacionSaldoFavor]),
|
||||
CONSTRAINT [CK_Vta_AplicacionesSaldoFavor_Importe] CHECK ([Importe] > (0))
|
||||
);
|
||||
GO
|
||||
|
||||
/* Relación exacta entre los renglones contables y el movimiento que los originó. */
|
||||
ALTER TABLE [dbo].[Conta_PrePolizasDetalle] ADD
|
||||
[CveSolicitud] int NULL,
|
||||
[CvePago] int NULL,
|
||||
[CveAplicacionSaldoFavor] int NULL;
|
||||
GO
|
||||
|
||||
/* ==================== LLAVES FORÁNEAS ==================== */
|
||||
|
||||
ALTER TABLE [dbo].[Fraccionamientos] ADD CONSTRAINT [FK_Fraccionamientos_Clientes]
|
||||
FOREIGN KEY ([CveCliente]) REFERENCES [dbo].[Clientes] ([CveCliente]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Fraccionamientos] ADD CONSTRAINT [FK_Fraccionamientos_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[FraccionamientosEtapas] ADD CONSTRAINT [FK_FraccionamientosEtapas_Fraccionamientos]
|
||||
FOREIGN KEY ([CveFraccionamiento]) REFERENCES [dbo].[Fraccionamientos] ([CveFraccionamiento]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[FraccionamientosEtapas] ADD CONSTRAINT [FK_FraccionamientosEtapas_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[FraccionamientosLotes] ADD CONSTRAINT [FK_FraccionamientosLotes_FraccionamientosEtapas]
|
||||
FOREIGN KEY ([CveFraccionamientoEtapa]) REFERENCES [dbo].[FraccionamientosEtapas] ([CveFraccionamientoEtapa]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[FraccionamientosLotes] ADD CONSTRAINT [FK_FraccionamientosLotes_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
|
||||
ALTER TABLE [dbo].[Tra_TiposTramite] ADD CONSTRAINT [FK_Tra_TiposTramite_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_TiposDocumento] ADD CONSTRAINT [FK_Tra_TiposDocumento_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_Preguntas] ADD CONSTRAINT [FK_Tra_Preguntas_Tra_TiposPregunta]
|
||||
FOREIGN KEY ([CveTipoPregunta]) REFERENCES [dbo].[Tra_TiposPregunta] ([CveTipoPregunta]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_Preguntas] ADD CONSTRAINT [FK_Tra_Preguntas_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_OpcionesPregunta] ADD CONSTRAINT [FK_Tra_OpcionesPregunta_Tra_Preguntas]
|
||||
FOREIGN KEY ([CvePregunta]) REFERENCES [dbo].[Tra_Preguntas] ([CvePregunta]);
|
||||
GO
|
||||
|
||||
ALTER TABLE [dbo].[Tra_Solicitudes] ADD CONSTRAINT [FK_Tra_Solicitudes_Clientes]
|
||||
FOREIGN KEY ([CveCliente]) REFERENCES [dbo].[Clientes] ([CveCliente]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_Solicitudes] ADD CONSTRAINT [FK_Tra_Solicitudes_Fraccionamientos_Cliente]
|
||||
FOREIGN KEY ([CveFraccionamiento], [CveCliente]) REFERENCES [dbo].[Fraccionamientos] ([CveFraccionamiento], [CveCliente]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_Solicitudes] ADD CONSTRAINT [FK_Tra_Solicitudes_FraccionamientosEtapas]
|
||||
FOREIGN KEY ([CveFraccionamientoEtapa], [CveFraccionamiento]) REFERENCES [dbo].[FraccionamientosEtapas] ([CveFraccionamientoEtapa], [CveFraccionamiento]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_Solicitudes] ADD CONSTRAINT [FK_Tra_Solicitudes_Tra_TiposTramite]
|
||||
FOREIGN KEY ([CveTipoTramite]) REFERENCES [dbo].[Tra_TiposTramite] ([CveTipoTramite]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_Solicitudes] ADD CONSTRAINT [FK_Tra_Solicitudes_Tra_EstatusSolicitud]
|
||||
FOREIGN KEY ([CveEstatusSolicitud]) REFERENCES [dbo].[Tra_EstatusSolicitud] ([CveEstatusSolicitud]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_Solicitudes] ADD CONSTRAINT [FK_Tra_Solicitudes_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuarioSolicita]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
|
||||
ALTER TABLE [dbo].[Tra_SolicitudesDependencias] ADD CONSTRAINT [FK_Tra_SolicitudesDependencias_Solicitud]
|
||||
FOREIGN KEY ([CveSolicitud]) REFERENCES [dbo].[Tra_Solicitudes] ([CveSolicitud]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_SolicitudesDependencias] ADD CONSTRAINT [FK_Tra_SolicitudesDependencias_Requerida]
|
||||
FOREIGN KEY ([CveSolicitudRequerida]) REFERENCES [dbo].[Tra_Solicitudes] ([CveSolicitud]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_SolicitudesDependencias] ADD CONSTRAINT [FK_Tra_SolicitudesDependencias_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
|
||||
ALTER TABLE [dbo].[Tra_HistorialEstatusSolicitud] ADD CONSTRAINT [FK_Tra_HistorialEstatusSolicitud_Solicitudes]
|
||||
FOREIGN KEY ([CveSolicitud]) REFERENCES [dbo].[Tra_Solicitudes] ([CveSolicitud]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_HistorialEstatusSolicitud] ADD CONSTRAINT [FK_Tra_HistorialEstatusSolicitud_Estatus]
|
||||
FOREIGN KEY ([CveEstatusSolicitud]) REFERENCES [dbo].[Tra_EstatusSolicitud] ([CveEstatusSolicitud]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_HistorialEstatusSolicitud] ADD CONSTRAINT [FK_Tra_HistorialEstatusSolicitud_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
|
||||
ALTER TABLE [dbo].[Tra_DocumentosRequeridos] ADD CONSTRAINT [FK_Tra_DocumentosRequeridos_TiposTramite]
|
||||
FOREIGN KEY ([CveTipoTramite]) REFERENCES [dbo].[Tra_TiposTramite] ([CveTipoTramite]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_DocumentosRequeridos] ADD CONSTRAINT [FK_Tra_DocumentosRequeridos_TiposDocumento]
|
||||
FOREIGN KEY ([CveTipoDocumento]) REFERENCES [dbo].[Tra_TiposDocumento] ([CveTipoDocumento]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_DocumentosSolicitud] ADD CONSTRAINT [FK_Tra_DocumentosSolicitud_Solicitudes]
|
||||
FOREIGN KEY ([CveSolicitud]) REFERENCES [dbo].[Tra_Solicitudes] ([CveSolicitud]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_DocumentosSolicitud] ADD CONSTRAINT [FK_Tra_DocumentosSolicitud_TiposDocumento]
|
||||
FOREIGN KEY ([CveTipoDocumento]) REFERENCES [dbo].[Tra_TiposDocumento] ([CveTipoDocumento]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_DocumentosSolicitud] ADD CONSTRAINT [FK_Tra_DocumentosSolicitud_UsuarioCarga]
|
||||
FOREIGN KEY ([CveUsuarioCarga]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_DocumentosSolicitud] ADD CONSTRAINT [FK_Tra_DocumentosSolicitud_UsuarioRevisa]
|
||||
FOREIGN KEY ([CveUsuarioRevisa]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
|
||||
ALTER TABLE [dbo].[Tra_PreguntasTramite] ADD CONSTRAINT [FK_Tra_PreguntasTramite_TiposTramite]
|
||||
FOREIGN KEY ([CveTipoTramite]) REFERENCES [dbo].[Tra_TiposTramite] ([CveTipoTramite]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_PreguntasTramite] ADD CONSTRAINT [FK_Tra_PreguntasTramite_Preguntas]
|
||||
FOREIGN KEY ([CvePregunta]) REFERENCES [dbo].[Tra_Preguntas] ([CvePregunta]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_RespuestasSolicitud] ADD CONSTRAINT [FK_Tra_RespuestasSolicitud_Solicitudes]
|
||||
FOREIGN KEY ([CveSolicitud]) REFERENCES [dbo].[Tra_Solicitudes] ([CveSolicitud]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_RespuestasSolicitud] ADD CONSTRAINT [FK_Tra_RespuestasSolicitud_Preguntas]
|
||||
FOREIGN KEY ([CvePregunta]) REFERENCES [dbo].[Tra_Preguntas] ([CvePregunta]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_RespuestasSolicitud] ADD CONSTRAINT [FK_Tra_RespuestasSolicitud_Opciones]
|
||||
FOREIGN KEY ([CveOpcionPregunta]) REFERENCES [dbo].[Tra_OpcionesPregunta] ([CveOpcionPregunta]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_RespuestasSolicitud] ADD CONSTRAINT [FK_Tra_RespuestasSolicitud_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
|
||||
ALTER TABLE [dbo].[Tra_CostosTramite] ADD CONSTRAINT [FK_Tra_CostosTramite_TiposTramite]
|
||||
FOREIGN KEY ([CveTipoTramite]) REFERENCES [dbo].[Tra_TiposTramite] ([CveTipoTramite]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_CostosTramite] ADD CONSTRAINT [FK_Tra_CostosTramite_ProductosServicios]
|
||||
FOREIGN KEY ([CvePS]) REFERENCES [dbo].[ProductosServicios] ([CvePS]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_CostosTramite] ADD CONSTRAINT [FK_Tra_CostosTramite_Gral_Monedas]
|
||||
FOREIGN KEY ([CveMoneda]) REFERENCES [dbo].[Gral_Monedas] ([CveMoneda]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_CostosTramite] ADD CONSTRAINT [FK_Tra_CostosTramite_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_NotasSolicitud] ADD CONSTRAINT [FK_Tra_NotasSolicitud_Solicitudes]
|
||||
FOREIGN KEY ([CveSolicitud]) REFERENCES [dbo].[Tra_Solicitudes] ([CveSolicitud]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Tra_NotasSolicitud] ADD CONSTRAINT [FK_Tra_NotasSolicitud_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
|
||||
ALTER TABLE [dbo].[VentasDetalle] ADD CONSTRAINT [FK_VentasDetalle_Ventas]
|
||||
FOREIGN KEY ([CveVenta]) REFERENCES [dbo].[Ventas] ([CveVenta]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[VentasDetalle] ADD CONSTRAINT [FK_VentasDetalle_ProductosServicios]
|
||||
FOREIGN KEY ([CvePS]) REFERENCES [dbo].[ProductosServicios] ([CvePS]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[VentasDetalle] ADD CONSTRAINT [FK_VentasDetalle_Tra_Solicitudes]
|
||||
FOREIGN KEY ([CveSolicitud]) REFERENCES [dbo].[Tra_Solicitudes] ([CveSolicitud]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[VentasDetalle] ADD CONSTRAINT [FK_VentasDetalle_Tra_CostosTramite]
|
||||
FOREIGN KEY ([CveCostoTramite]) REFERENCES [dbo].[Tra_CostosTramite] ([CveCostoTramite]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[VentasDetalle] ADD CONSTRAINT [FK_VentasDetalle_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
|
||||
ALTER TABLE [dbo].[Vta_Pagos] ADD CONSTRAINT [FK_Vta_Pagos_Clientes]
|
||||
FOREIGN KEY ([CveCliente]) REFERENCES [dbo].[Clientes] ([CveCliente]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Vta_Pagos] ADD CONSTRAINT [FK_Vta_Pagos_Ventas]
|
||||
FOREIGN KEY ([CveVenta]) REFERENCES [dbo].[Ventas] ([CveVenta]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Vta_Pagos] ADD CONSTRAINT [FK_Vta_Pagos_Tra_Solicitudes]
|
||||
FOREIGN KEY ([CveSolicitud]) REFERENCES [dbo].[Tra_Solicitudes] ([CveSolicitud]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Vta_Pagos] ADD CONSTRAINT [FK_Vta_Pagos_Vta_FormaPago]
|
||||
FOREIGN KEY ([CveFormaPago]) REFERENCES [dbo].[Vta_FormaPago] ([CveFormaPago]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Vta_Pagos] ADD CONSTRAINT [FK_Vta_Pagos_Gral_Monedas]
|
||||
FOREIGN KEY ([CveMoneda]) REFERENCES [dbo].[Gral_Monedas] ([CveMoneda]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Vta_Pagos] ADD CONSTRAINT [FK_Vta_Pagos_Conta_PrePolizas]
|
||||
FOREIGN KEY ([CvePrePoliza]) REFERENCES [dbo].[Conta_PrePolizas] ([CvePrePoliza]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Vta_Pagos] ADD CONSTRAINT [FK_Vta_Pagos_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
|
||||
ALTER TABLE [dbo].[Vta_AplicacionesSaldoFavor] ADD CONSTRAINT [FK_Vta_AplicacionesSaldoFavor_PagoOrigen]
|
||||
FOREIGN KEY ([CvePagoOrigen]) REFERENCES [dbo].[Vta_Pagos] ([CvePago]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Vta_AplicacionesSaldoFavor] ADD CONSTRAINT [FK_Vta_AplicacionesSaldoFavor_VentaDestino]
|
||||
FOREIGN KEY ([CveVentaDestino]) REFERENCES [dbo].[Ventas] ([CveVenta]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Vta_AplicacionesSaldoFavor] ADD CONSTRAINT [FK_Vta_AplicacionesSaldoFavor_DetalleDestino]
|
||||
FOREIGN KEY ([CveVentaDetalleDestino]) REFERENCES [dbo].[VentasDetalle] ([CveVentaDetalle]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Vta_AplicacionesSaldoFavor] ADD CONSTRAINT [FK_Vta_AplicacionesSaldoFavor_Solicitud]
|
||||
FOREIGN KEY ([CveSolicitud]) REFERENCES [dbo].[Tra_Solicitudes] ([CveSolicitud]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Vta_AplicacionesSaldoFavor] ADD CONSTRAINT [FK_Vta_AplicacionesSaldoFavor_PrePoliza]
|
||||
FOREIGN KEY ([CvePrePoliza]) REFERENCES [dbo].[Conta_PrePolizas] ([CvePrePoliza]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Vta_AplicacionesSaldoFavor] ADD CONSTRAINT [FK_Vta_AplicacionesSaldoFavor_Sis_Usuarios]
|
||||
FOREIGN KEY ([CveUsuario]) REFERENCES [dbo].[Sis_Usuarios] ([CveUsuario]);
|
||||
GO
|
||||
|
||||
ALTER TABLE [dbo].[Conta_PrePolizasDetalle] ADD CONSTRAINT [FK_Conta_PrePolizasDetalle_Tra_Solicitudes]
|
||||
FOREIGN KEY ([CveSolicitud]) REFERENCES [dbo].[Tra_Solicitudes] ([CveSolicitud]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Conta_PrePolizasDetalle] ADD CONSTRAINT [FK_Conta_PrePolizasDetalle_Vta_Pagos]
|
||||
FOREIGN KEY ([CvePago]) REFERENCES [dbo].[Vta_Pagos] ([CvePago]);
|
||||
GO
|
||||
ALTER TABLE [dbo].[Conta_PrePolizasDetalle] ADD CONSTRAINT [FK_Conta_PrePolizasDetalle_Vta_AplicacionesSaldoFavor]
|
||||
FOREIGN KEY ([CveAplicacionSaldoFavor]) REFERENCES [dbo].[Vta_AplicacionesSaldoFavor] ([CveAplicacionSaldoFavor]);
|
||||
GO
|
||||
|
||||
/* ==================== ÍNDICES DE OPERACIÓN ==================== */
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_Tra_Solicitudes_Cliente_Fraccionamiento_Etapa]
|
||||
ON [dbo].[Tra_Solicitudes] ([CveCliente], [CveFraccionamiento], [CveFraccionamientoEtapa]);
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_Tra_Solicitudes_Estatus]
|
||||
ON [dbo].[Tra_Solicitudes] ([CveEstatusSolicitud], [FechaAlta]);
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_Tra_HistorialEstatusSolicitud_Solicitud_Fecha]
|
||||
ON [dbo].[Tra_HistorialEstatusSolicitud] ([CveSolicitud], [FechaAlta]);
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_VentasDetalle_Solicitud]
|
||||
ON [dbo].[VentasDetalle] ([CveSolicitud]) INCLUDE ([CveVenta], [CvePS], [Importe]);
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_Vta_Pagos_Cliente_Venta]
|
||||
ON [dbo].[Vta_Pagos] ([CveCliente], [CveVenta], [EsActivo]);
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_Vta_AplicacionesSaldoFavor_PagoOrigen]
|
||||
ON [dbo].[Vta_AplicacionesSaldoFavor] ([CvePagoOrigen], [EsActivo]) INCLUDE ([Importe], [CveSolicitud]);
|
||||
GO
|
||||
|
||||
/* El saldo nunca se almacena: se calcula del pago recibido menos sus aplicaciones activas. */
|
||||
CREATE VIEW [dbo].[Vta_SaldosFavor]
|
||||
AS
|
||||
SELECT
|
||||
P.[CvePago],
|
||||
P.[CveCliente],
|
||||
P.[CveVenta],
|
||||
P.[CveMoneda],
|
||||
P.[Importe] AS [ImporteRecibido],
|
||||
ISNULL(SUM(CASE WHEN A.[EsActivo] = (1) THEN A.[Importe] ELSE (0) END), (0)) AS [ImporteAplicado],
|
||||
P.[Importe] - ISNULL(SUM(CASE WHEN A.[EsActivo] = (1) THEN A.[Importe] ELSE (0) END), (0)) AS [SaldoDisponible]
|
||||
FROM [dbo].[Vta_Pagos] AS P
|
||||
LEFT JOIN [dbo].[Vta_AplicacionesSaldoFavor] AS A
|
||||
ON A.[CvePagoOrigen] = P.[CvePago]
|
||||
WHERE P.[EsSaldoFavor] = (1)
|
||||
AND P.[EsActivo] = (1)
|
||||
GROUP BY P.[CvePago], P.[CveCliente], P.[CveVenta], P.[CveMoneda], P.[Importe];
|
||||
GO
|
||||
20
LOCAL_SETUP_COMMANDS.ps1.txt
Normal file
20
LOCAL_SETUP_COMMANDS.ps1.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
# Run these commands in PowerShell.
|
||||
# Adjust $OldProject only if your previous project is in another folder.
|
||||
|
||||
$Target = "C:\Proyectos\Agua de hermosillo\Plataforma de Desarrolladores"
|
||||
$OldProject = "C:\Ruta\ProyectoAnterior\Plataforma de Desarrolladores"
|
||||
New-Item -ItemType Directory -Force -Path $Target | Out-Null
|
||||
|
||||
# Extract this ZIP into $Target first, or copy the SIG.WebForms.Starter folder content there.
|
||||
|
||||
# Copy existing static folders exactly as-is:
|
||||
robocopy "$OldProject\Styles" "$Target\SIG.Web\Styles" /E
|
||||
robocopy "$OldProject\Scripts" "$Target\SIG.Web\Scripts" /E
|
||||
robocopy "$OldProject\Metronic" "$Target\SIG.Web\Metronic" /E
|
||||
|
||||
# Open the solution:
|
||||
Start-Process "$Target\SIG.WebForms.Starter.sln"
|
||||
|
||||
# In Visual Studio Package Manager Console, run:
|
||||
# Update-Package -Reinstall
|
||||
423
Procedimientos_Seguridad_Actuales.sql
Normal file
423
Procedimientos_Seguridad_Actuales.sql
Normal file
@@ -0,0 +1,423 @@
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PA_Sis_UsuarioGetByName]
|
||||
@NombreUsuario varchar(100),
|
||||
@CveUsuarioEjecuta int,
|
||||
@ErrorMostrar varchar(max) OUTPUT,
|
||||
@ErrorTipo int OUTPUT,
|
||||
@Return bit OUTPUT
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SELECT @ErrorMostrar = NULL, @ErrorTipo = 0, @Return = 0;
|
||||
|
||||
BEGIN TRY
|
||||
SELECT TOP (1)
|
||||
U.[CveUsuario], U.[NombreUsuario], U.[HashContrasena], U.[CorreoElectronico],
|
||||
U.[CveRol], R.[NombreRol], U.[Activo], U.[DebeCambiarContrasena],
|
||||
U.[IntentosFallidosInicioSesion], U.[BloqueadoHasta],
|
||||
U.[FechaUltimoInicioSesion], U.[FechaUltimaConexion], U.[CorreoVerificado]
|
||||
FROM [dbo].[Sis_Usuarios] AS U INNER JOIN [dbo].[Sis_Roles] AS R ON R.[CveRol] = U.[CveRol]
|
||||
WHERE U.[NombreUsuario] = @NombreUsuario
|
||||
AND U.[Borrado] = (0)
|
||||
AND R.[Borrado] = (0);
|
||||
|
||||
SET @Return = 1;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
SET @ErrorMostrar = 'Ocurrió un error al consultar el usuario; favor de contactar a Sistemas.';
|
||||
SET @ErrorTipo = 1;
|
||||
SET @Return = 0;
|
||||
DECLARE @ErrorTecnico nvarchar(2048) = CONCAT('Número: ', ERROR_NUMBER(), '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Sis_UsuarioGetByName'), '; Línea: ', ERROR_LINE(), '; Detalle: ', ERROR_MESSAGE());
|
||||
RAISERROR(N'%s', 10, 1, @ErrorTecnico);
|
||||
END CATCH;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PA_Sis_UsuarioLoginSuccessUpdate]
|
||||
@CveUsuario int,
|
||||
@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
|
||||
UPDATE [dbo].[Sis_Usuarios]
|
||||
SET [FechaUltimoInicioSesion] = GETDATE(),
|
||||
[FechaUltimaConexion] = GETDATE(),
|
||||
[IntentosFallidosInicioSesion] = (0),
|
||||
[BloqueadoHasta] = NULL,
|
||||
[FechaActualizacion] = GETDATE()
|
||||
WHERE [CveUsuario] = @CveUsuario
|
||||
AND [Borrado] = (0);
|
||||
|
||||
IF @@ROWCOUNT = (0)
|
||||
BEGIN
|
||||
SET @ErrorMostrar = 'El usuario ya no está disponible.';
|
||||
SET @ErrorTipo = 2;
|
||||
RETURN;
|
||||
END;
|
||||
|
||||
SET @Return = 1;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
SET @ErrorMostrar = 'Ocurrió un error al actualizar el inicio de sesión; favor de contactar a Sistemas.';
|
||||
SET @ErrorTipo = 1;
|
||||
SET @Return = 0;
|
||||
DECLARE @ErrorTecnico nvarchar(2048) = CONCAT('Número: ', ERROR_NUMBER(), '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Sis_UsuarioLoginSuccessUpdate'), '; Línea: ', ERROR_LINE(), '; Detalle: ', ERROR_MESSAGE());
|
||||
RAISERROR(N'%s', 10, 1, @ErrorTecnico);
|
||||
END CATCH;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PA_Sis_UsuarioLoginFailedUpdate]
|
||||
@CveUsuario int,
|
||||
@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
|
||||
UPDATE [dbo].[Sis_Usuarios]
|
||||
SET [IntentosFallidosInicioSesion] = [IntentosFallidosInicioSesion] + (1),
|
||||
[BloqueadoHasta] = CASE
|
||||
WHEN [IntentosFallidosInicioSesion] + (1) >= (5)
|
||||
THEN DATEADD(MINUTE, (15), GETDATE())
|
||||
ELSE [BloqueadoHasta]
|
||||
END,
|
||||
[FechaActualizacion] = GETDATE()
|
||||
WHERE [CveUsuario] = @CveUsuario
|
||||
AND [Borrado] = (0);
|
||||
|
||||
IF @@ROWCOUNT = (0)
|
||||
BEGIN
|
||||
SET @ErrorMostrar = 'El usuario ya no está disponible.';
|
||||
SET @ErrorTipo = 2;
|
||||
RETURN;
|
||||
END;
|
||||
|
||||
SET @Return = 1;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
SET @ErrorMostrar = 'Ocurrió un error al registrar el intento de acceso; favor de contactar a Sistemas.';
|
||||
SET @ErrorTipo = 1;
|
||||
SET @Return = 0;
|
||||
DECLARE @ErrorTecnico nvarchar(2048) = CONCAT('Número: ', ERROR_NUMBER(), '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Sis_UsuarioLoginFailedUpdate'), '; Línea: ', ERROR_LINE(), '; Detalle: ', ERROR_MESSAGE());
|
||||
RAISERROR(N'%s', 10, 1, @ErrorTecnico);
|
||||
END CATCH;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PA_Sis_IntentoInicioSesionAdd]
|
||||
@CveUsuario int = NULL,
|
||||
@NombreUsuario varchar(100),
|
||||
@Exitoso bit,
|
||||
@MotivoFallo varchar(200) = NULL,
|
||||
@DireccionIP varchar(50) = NULL,
|
||||
@AgenteUsuario varchar(1000) = NULL,
|
||||
@NombreDominio varchar(255) = NULL,
|
||||
@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
|
||||
INSERT INTO [dbo].[Sis_IntentosInicioSesion]
|
||||
([CveUsuario], [NombreUsuario], [Exitoso], [MotivoFallo],
|
||||
[DireccionIP], [AgenteUsuario], [NombreDominio])
|
||||
VALUES
|
||||
(@CveUsuario, @NombreUsuario, @Exitoso, @MotivoFallo,
|
||||
@DireccionIP, @AgenteUsuario, @NombreDominio);
|
||||
|
||||
SET @Return = 1;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
SET @ErrorMostrar = 'Ocurrió un error al registrar el intento de acceso; favor de contactar a Sistemas.';
|
||||
SET @ErrorTipo = 1;
|
||||
SET @Return = 0;
|
||||
DECLARE @ErrorTecnico nvarchar(2048) = CONCAT('Número: ', ERROR_NUMBER(), '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Sis_IntentoInicioSesionAdd'), '; Línea: ', ERROR_LINE(), '; Detalle: ', ERROR_MESSAGE());
|
||||
RAISERROR(N'%s', 10, 1, @ErrorTecnico);
|
||||
END CATCH;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PA_Sis_UsuarioSesionAdd]
|
||||
@CveUsuario int,
|
||||
@TokenSesion varchar(200),
|
||||
@DireccionIP varchar(50) = NULL,
|
||||
@AgenteUsuario varchar(1000) = NULL,
|
||||
@NombreDominio varchar(255) = NULL,
|
||||
@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
|
||||
IF NOT EXISTS
|
||||
(
|
||||
SELECT 1 FROM [dbo].[Sis_Usuarios]
|
||||
WHERE [CveUsuario] = @CveUsuario AND [Activo] = (1) AND [Borrado] = (0)
|
||||
)
|
||||
BEGIN
|
||||
SET @ErrorMostrar = 'El usuario no está disponible para iniciar sesión.';
|
||||
SET @ErrorTipo = 2;
|
||||
RETURN;
|
||||
END;
|
||||
|
||||
INSERT INTO [dbo].[Sis_UsuariosSesiones]
|
||||
([CveUsuario], [TokenSesion], [DireccionIP], [AgenteUsuario],
|
||||
[NombreDominio], [FechaUltimaActividad])
|
||||
VALUES
|
||||
(@CveUsuario, @TokenSesion, @DireccionIP, @AgenteUsuario,
|
||||
@NombreDominio, GETDATE());
|
||||
|
||||
SET @Return = 1;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
SET @ErrorMostrar = 'Ocurrió un error al crear la sesión; favor de contactar a Sistemas.';
|
||||
SET @ErrorTipo = 1;
|
||||
SET @Return = 0;
|
||||
DECLARE @ErrorTecnico nvarchar(2048) = CONCAT('Número: ', ERROR_NUMBER(), '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Sis_UsuarioSesionAdd'), '; Línea: ', ERROR_LINE(), '; Detalle: ', ERROR_MESSAGE());
|
||||
RAISERROR(N'%s', 10, 1, @ErrorTecnico);
|
||||
END CATCH;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PA_Sis_UsuarioSesionEnd]
|
||||
@TokenSesion varchar(200),
|
||||
@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
|
||||
UPDATE [dbo].[Sis_UsuariosSesiones]
|
||||
SET [FechaFin] = GETDATE(), [Activo] = (0)
|
||||
WHERE [TokenSesion] = @TokenSesion
|
||||
AND [Borrado] = (0);
|
||||
|
||||
/* Cerrar una sesión inexistente o ya cerrada es una operación idempotente. */
|
||||
SET @Return = 1;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
SET @ErrorMostrar = 'Ocurrió un error al cerrar la sesión; favor de contactar a Sistemas.';
|
||||
SET @ErrorTipo = 1;
|
||||
SET @Return = 0;
|
||||
DECLARE @ErrorTecnico nvarchar(2048) = CONCAT('Número: ', ERROR_NUMBER(), '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Sis_UsuarioSesionEnd'), '; Línea: ', ERROR_LINE(), '; Detalle: ', ERROR_MESSAGE());
|
||||
RAISERROR(N'%s', 10, 1, @ErrorTecnico);
|
||||
END CATCH;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PA_Sis_PermisoCheck]
|
||||
@CodigoPermiso varchar(100),
|
||||
@CveUsuarioEjecuta int,
|
||||
@ErrorMostrar varchar(max) OUTPUT,
|
||||
@ErrorTipo int OUTPUT,
|
||||
@Return bit OUTPUT
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SELECT @ErrorMostrar = NULL, @ErrorTipo = 0, @Return = 0;
|
||||
|
||||
BEGIN TRY
|
||||
DECLARE @CveRol int;
|
||||
DECLARE @PermisoUsuario bit;
|
||||
|
||||
SELECT @CveRol = [CveRol]
|
||||
FROM [dbo].[Sis_Usuarios]
|
||||
WHERE [CveUsuario] = @CveUsuarioEjecuta
|
||||
AND [Activo] = (1)
|
||||
AND [Borrado] = (0);
|
||||
|
||||
SELECT TOP (1) @PermisoUsuario = UP.[Permitido]
|
||||
FROM [dbo].[Sis_UsuariosPermisos] AS UP
|
||||
INNER JOIN [dbo].[Sis_Permisos] AS P ON P.[CvePermiso] = UP.[CvePermiso]
|
||||
WHERE UP.[CveUsuario] = @CveUsuarioEjecuta
|
||||
AND P.[CodigoPermiso] = @CodigoPermiso
|
||||
AND P.[Activo] = (1)
|
||||
AND UP.[Borrado] = (0)
|
||||
AND P.[Borrado] = (0);
|
||||
|
||||
IF @PermisoUsuario IS NOT NULL
|
||||
SELECT CAST(@PermisoUsuario AS bit) AS [Permitido];
|
||||
ELSE IF EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM [dbo].[Sis_RolesPermisos] AS RP
|
||||
INNER JOIN [dbo].[Sis_Permisos] AS P ON P.[CvePermiso] = RP.[CvePermiso]
|
||||
WHERE RP.[CveRol] = @CveRol
|
||||
AND P.[CodigoPermiso] = @CodigoPermiso
|
||||
AND RP.[Permitido] = (1)
|
||||
AND P.[Activo] = (1)
|
||||
AND RP.[Borrado] = (0)
|
||||
AND P.[Borrado] = (0)
|
||||
)
|
||||
SELECT CAST((1) AS bit) AS [Permitido];
|
||||
ELSE
|
||||
SELECT CAST((0) AS bit) AS [Permitido];
|
||||
|
||||
SET @Return = 1;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
SET @ErrorMostrar = 'Ocurrió un error al consultar los permisos; favor de contactar a Sistemas.';
|
||||
SET @ErrorTipo = 1;
|
||||
SET @Return = 0;
|
||||
DECLARE @ErrorTecnico nvarchar(2048) = CONCAT('Número: ', ERROR_NUMBER(), '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Sis_PermisoCheck'), '; Línea: ', ERROR_LINE(), '; Detalle: ', ERROR_MESSAGE());
|
||||
RAISERROR(N'%s', 10, 1, @ErrorTecnico);
|
||||
END CATCH;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PA_Sis_PaginaAccessCheck]
|
||||
@PaginaUrl varchar(500),
|
||||
@CveUsuarioEjecuta int,
|
||||
@ErrorMostrar varchar(max) OUTPUT,
|
||||
@ErrorTipo int OUTPUT,
|
||||
@Return bit OUTPUT
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SELECT @ErrorMostrar = NULL, @ErrorTipo = 0, @Return = 0;
|
||||
|
||||
BEGIN TRY
|
||||
DECLARE @CveRol int;
|
||||
DECLARE @PuedeAcceder bit = (0);
|
||||
|
||||
SELECT @CveRol = [CveRol]
|
||||
FROM [dbo].[Sis_Usuarios]
|
||||
WHERE [CveUsuario] = @CveUsuarioEjecuta
|
||||
AND [Activo] = (1)
|
||||
AND [Borrado] = (0);
|
||||
|
||||
IF EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM [dbo].[Sis_Modulos] AS M
|
||||
INNER JOIN [dbo].[Sis_RolesModulos] AS RM ON RM.[CveModulo] = M.[CveModulo]
|
||||
WHERE RM.[CveRol] = @CveRol
|
||||
AND RM.[PuedeVer] = (1)
|
||||
AND M.[Activo] = (1)
|
||||
AND M.[Visible] = (1)
|
||||
AND RM.[Borrado] = (0)
|
||||
AND M.[Borrado] = (0)
|
||||
AND
|
||||
(
|
||||
LOWER(REPLACE(M.[Url], '~/', '')) = LOWER(@PaginaUrl)
|
||||
OR LOWER(REPLACE(M.[Url], '/', '')) = LOWER(REPLACE(@PaginaUrl, '/', ''))
|
||||
)
|
||||
)
|
||||
SET @PuedeAcceder = (1);
|
||||
ELSE IF EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM [dbo].[Sis_Modulos] AS M
|
||||
WHERE M.[Activo] = (1)
|
||||
AND M.[Borrado] = (0)
|
||||
AND LOWER(REPLACE(M.[Url], '~/', '')) = LOWER(@PaginaUrl)
|
||||
)
|
||||
SET @PuedeAcceder = (0);
|
||||
ELSE IF EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM [dbo].[Sis_UsuariosPermisos] AS UP
|
||||
INNER JOIN [dbo].[Sis_Permisos] AS P ON P.[CvePermiso] = UP.[CvePermiso]
|
||||
WHERE UP.[CveUsuario] = @CveUsuarioEjecuta
|
||||
AND P.[CodigoPermiso] = 'System.Access'
|
||||
AND UP.[Permitido] = (1)
|
||||
AND P.[Activo] = (1)
|
||||
AND UP.[Borrado] = (0)
|
||||
AND P.[Borrado] = (0)
|
||||
)
|
||||
SET @PuedeAcceder = (1);
|
||||
ELSE IF EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM [dbo].[Sis_RolesPermisos] AS RP
|
||||
INNER JOIN [dbo].[Sis_Permisos] AS P ON P.[CvePermiso] = RP.[CvePermiso]
|
||||
WHERE RP.[CveRol] = @CveRol
|
||||
AND P.[CodigoPermiso] = 'System.Access'
|
||||
AND RP.[Permitido] = (1)
|
||||
AND P.[Activo] = (1)
|
||||
AND RP.[Borrado] = (0)
|
||||
AND P.[Borrado] = (0)
|
||||
)
|
||||
SET @PuedeAcceder = (1);
|
||||
|
||||
SELECT @PuedeAcceder AS [PuedeAcceder];
|
||||
SET @Return = 1;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
SET @ErrorMostrar = 'Ocurrió un error al validar el acceso; favor de contactar a Sistemas.';
|
||||
SET @ErrorTipo = 1;
|
||||
SET @Return = 0;
|
||||
DECLARE @ErrorTecnico nvarchar(2048) = CONCAT('Número: ', ERROR_NUMBER(), '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Sis_PaginaAccessCheck'), '; Línea: ', ERROR_LINE(), '; Detalle: ', ERROR_MESSAGE());
|
||||
RAISERROR(N'%s', 10, 1, @ErrorTecnico);
|
||||
END CATCH;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[PA_Sis_MenuList]
|
||||
@CveRol int,
|
||||
@CveUsuarioEjecuta int,
|
||||
@ErrorMostrar varchar(max) OUTPUT,
|
||||
@ErrorTipo int OUTPUT,
|
||||
@Return bit OUTPUT
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SELECT @ErrorMostrar = NULL, @ErrorTipo = 0, @Return = 0;
|
||||
|
||||
BEGIN TRY
|
||||
SELECT DISTINCT
|
||||
M.[CveModulo], M.[CveModuloPadre], M.[NombreModulo], M.[Url],
|
||||
M.[ClaseCssIcono], M.[ClaseCssEstilo], M.[OrdenVisualizacion], M.[Visible]
|
||||
FROM [dbo].[Sis_Modulos] AS M
|
||||
INNER JOIN [dbo].[Sis_RolesModulos] AS RM ON RM.[CveModulo] = M.[CveModulo]
|
||||
WHERE RM.[CveRol] = @CveRol
|
||||
AND RM.[PuedeVer] = (1)
|
||||
AND M.[Activo] = (1)
|
||||
AND M.[Visible] = (1)
|
||||
AND RM.[Borrado] = (0)
|
||||
AND M.[Borrado] = (0)
|
||||
ORDER BY M.[OrdenVisualizacion], M.[NombreModulo];
|
||||
|
||||
SET @Return = 1;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
SET @ErrorMostrar = 'Ocurrió un error al consultar el menú; favor de contactar a Sistemas.';
|
||||
SET @ErrorTipo = 1;
|
||||
SET @Return = 0;
|
||||
DECLARE @ErrorTecnico nvarchar(2048) = CONCAT('Número: ', ERROR_NUMBER(), '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Sis_MenuList'), '; Línea: ', ERROR_LINE(), '; Detalle: ', ERROR_MESSAGE());
|
||||
RAISERROR(N'%s', 10, 1, @ErrorTecnico);
|
||||
END CATCH;
|
||||
END;
|
||||
GO
|
||||
100
README.md
Normal file
100
README.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Plataforma de Desarrolladores Agua de Hermosillo
|
||||
|
||||
Aplicación ASP.NET WebForms para administrar desarrolladores, clientes, fraccionamientos, trámites, pagos y su futura integración contable.
|
||||
|
||||
## Tecnología
|
||||
|
||||
- ASP.NET WebForms con C#.
|
||||
- .NET Framework 4.8.1.
|
||||
- SQL Server y procedimientos almacenados.
|
||||
- Metronic como base visual.
|
||||
- AjaxControlToolkit y `UpdatePanel` para actualizaciones parciales.
|
||||
- Visual Studio 2022.
|
||||
|
||||
## Estructura principal
|
||||
|
||||
```text
|
||||
SIG.Web/
|
||||
AppCode/
|
||||
Contracts/
|
||||
Models/
|
||||
Repositories/
|
||||
Services/
|
||||
Security/
|
||||
Utilities/
|
||||
Pages/
|
||||
Security/
|
||||
Tramites/
|
||||
Polizas/
|
||||
Metronic/
|
||||
Scripts/
|
||||
Styles/
|
||||
Site.master
|
||||
Login.aspx
|
||||
Inicio.aspx
|
||||
Web.config
|
||||
```
|
||||
|
||||
Las páginas y la página maestra consumen contratos y servicios. El acceso a SQL se mantiene en repositorios y ejecutores de base de datos.
|
||||
|
||||
```text
|
||||
ASPX / Site.master
|
||||
-> Contracts
|
||||
-> Services
|
||||
-> Repositories / SQL helpers
|
||||
-> SQL Server
|
||||
```
|
||||
|
||||
## Requisitos
|
||||
|
||||
1. Windows con IIS Express.
|
||||
2. Visual Studio 2022 con la carga de trabajo **Desarrollo de ASP.NET y web**.
|
||||
3. .NET Framework 4.8.1 Developer Pack.
|
||||
4. SQL Server accesible desde el equipo local.
|
||||
|
||||
## Configuración local
|
||||
|
||||
1. Abrir `SIG.WebForms.Starter.sln` en Visual Studio 2022.
|
||||
2. Restaurar los paquetes NuGet.
|
||||
3. Revisar la cadena `Conexion` de `SIG.Web/Web.config`.
|
||||
4. Crear o actualizar la base de datos con `Esquema_BD_AguaHillo_Idempotente.sql`.
|
||||
5. Para instalar solamente usuarios, clientes y verificación de correo, utilizar `Usuarios_Clientes_Registro.sql`.
|
||||
6. Compilar la solución en `Debug` y ejecutarla con IIS Express.
|
||||
|
||||
La ruta raíz utiliza `Inicio.aspx` como documento predeterminado. Los usuarios sin sesión son enviados a `Login.aspx`, conservando la ruta solicitada en `retornoURL`.
|
||||
|
||||
## Correo de verificación
|
||||
|
||||
El autorregistro requiere configurar estos valores en `SIG.Web/Web.config`:
|
||||
|
||||
```xml
|
||||
<add key="SmtpHost" value="" />
|
||||
<add key="SmtpPort" value="587" />
|
||||
<add key="SmtpEnableSsl" value="true" />
|
||||
<add key="SmtpUser" value="" />
|
||||
<add key="SmtpPassword" value="" />
|
||||
<add key="EmailFrom" value="" />
|
||||
```
|
||||
|
||||
No deben confirmarse credenciales reales en el repositorio. Para cada ambiente, coloque los valores mediante una transformación segura o durante el despliegue.
|
||||
|
||||
## Módulo de usuarios
|
||||
|
||||
- `Pages/Security/Usuarios.aspx`: administración autenticada de usuarios y clientes relacionados.
|
||||
- `Pages/Security/RegistroCliente.aspx`: autorregistro público en dos pasos.
|
||||
- `Pages/Security/RegistroPendiente.aspx`: aviso para revisar el correo.
|
||||
- `Pages/Security/VerificarCorreo.aspx`: validación de la liga y activación de la cuenta.
|
||||
|
||||
El registro conserva temporalmente los datos de ambos pasos en `ViewState` cifrado. Usuario, cliente, relación y token se guardan juntos únicamente después de concluir las validaciones.
|
||||
|
||||
## Reglas de base de datos
|
||||
|
||||
- Las operaciones desde C# utilizan procedimientos almacenados.
|
||||
- Los procedimientos incluyen `TRY/CATCH`, `@ErrorMostrar`, `@ErrorTipo` y `@Return`.
|
||||
- Las entradas se envían como parámetros; no se concatena información del usuario en SQL.
|
||||
- El borrado funcional es lógico mediante `Borrado` y `FechaBorrado`.
|
||||
- Los errores registran el procedimiento y parámetros de diagnóstico, ocultando valores sensibles.
|
||||
|
||||
## Archivos que no se versionan
|
||||
|
||||
El `.gitignore` excluye compilados, paquetes restaurables, configuración personal de Visual Studio, logs, bases locales, temporales, publicaciones y carpetas de trabajo local. Las carpetas visuales `Metronic`, `Styles` y `Scripts` sí forman parte del proyecto.
|
||||
105
SIG.Web/AccessDenied.aspx
Normal file
105
SIG.Web/AccessDenied.aspx
Normal file
@@ -0,0 +1,105 @@
|
||||
<%@ Page Title="Acceso denegado" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeBehind="AccessDenied.aspx.cs" Inherits="SIG.Web.AccessDenied" %>
|
||||
|
||||
<asp:Content ID="ContentHead" ContentPlaceHolderID="H" runat="server">
|
||||
<style type="text/css">
|
||||
.access-denied-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: calc(100vh - 190px);
|
||||
padding: 30px 15px;
|
||||
}
|
||||
|
||||
.access-denied-card {
|
||||
width: 100%;
|
||||
max-width: 680px;
|
||||
padding: 42px 38px;
|
||||
background: #ffffff;
|
||||
border-top: 4px solid #e7505a;
|
||||
box-shadow: 0 4px 18px rgba(0, 0, 0, .10);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.access-denied-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
margin-bottom: 18px;
|
||||
border-radius: 50%;
|
||||
color: #e7505a;
|
||||
background: #fbeaec;
|
||||
font-size: 38px;
|
||||
}
|
||||
|
||||
.access-denied-code {
|
||||
margin: 0 0 4px;
|
||||
color: #e7505a;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.access-denied-title {
|
||||
margin: 0 0 14px;
|
||||
color: #34495e;
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.access-denied-message {
|
||||
max-width: 520px;
|
||||
margin: 0 auto 28px;
|
||||
color: #697882;
|
||||
font-size: 15px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.access-denied-actions .btn {
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.access-denied-card {
|
||||
padding: 32px 20px;
|
||||
}
|
||||
|
||||
.access-denied-title {
|
||||
font-size: 25px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</asp:Content>
|
||||
|
||||
<asp:Content ID="ContentMain" ContentPlaceHolderID="C" runat="server">
|
||||
<div class="access-denied-wrapper">
|
||||
<section class="access-denied-card" aria-labelledby="accessDeniedTitle">
|
||||
<div class="access-denied-icon" aria-hidden="true">
|
||||
<i class="fa fa-lock"></i>
|
||||
</div>
|
||||
|
||||
<p class="access-denied-code">ERROR 403</p>
|
||||
<h1 id="accessDeniedTitle" class="access-denied-title">Acceso denegado</h1>
|
||||
<p class="access-denied-message">
|
||||
Tu sesión está activa, pero tu usuario no cuenta con permiso para consultar esta página.
|
||||
Si necesitas acceso, solicita la autorización correspondiente al administrador del sistema.
|
||||
</p>
|
||||
|
||||
<div class="access-denied-actions">
|
||||
<a class="btn blue" href="<%= ResolveUrl("~/Inicio.aspx") %>">
|
||||
<i class="fa fa-home"></i> Ir al inicio
|
||||
</a>
|
||||
<button type="button" class="btn default" onclick="window.history.back();">
|
||||
<i class="fa fa-arrow-left"></i> Regresar
|
||||
</button>
|
||||
<a class="btn red" href="<%= ResolveUrl("~/Logout.aspx") %>">
|
||||
<i class="fa fa-sign-out"></i> Cerrar sesión
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</asp:Content>
|
||||
|
||||
<asp:Content ID="ContentScripts" ContentPlaceHolderID="J" runat="server">
|
||||
</asp:Content>
|
||||
14
SIG.Web/AccessDenied.aspx.cs
Normal file
14
SIG.Web/AccessDenied.aspx.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Web.UI;
|
||||
|
||||
namespace SIG.Web
|
||||
{
|
||||
public partial class AccessDenied : Page
|
||||
{
|
||||
protected void Page_Load(object sender, EventArgs e)
|
||||
{
|
||||
Response.StatusCode = 403;
|
||||
Response.TrySkipIisCustomErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
SIG.Web/AccessDenied.aspx.designer.cs
generated
Normal file
6
SIG.Web/AccessDenied.aspx.designer.cs
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace SIG.Web
|
||||
{
|
||||
public partial class AccessDenied
|
||||
{
|
||||
}
|
||||
}
|
||||
63
SIG.Web/AjaxControlToolkit.config
Normal file
63
SIG.Web/AjaxControlToolkit.config
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ajaxControlToolkit>
|
||||
<controlBundles>
|
||||
<controlBundle name="Accordion">
|
||||
<control name="AccordionExtender"></control>
|
||||
<control name="CollapsiblePanelExtender"></control>
|
||||
</controlBundle>
|
||||
<!--<controlBundle name="CustomControls">
|
||||
<control name="AjaxControlToolkitWelcomeLabel.ActWelcomeLabel" assembly="CustomAjaxServerControlSample"></control>
|
||||
<control name="CollapsiblePanelExtender"></control>
|
||||
</controlBundle>-->
|
||||
<controlBundle>
|
||||
<control name="AjaxFileUpload"></control>
|
||||
<control name="AlwaysVisibleControlExtender"></control>
|
||||
<control name="AreaChart"></control>
|
||||
<control name="AsyncFileUpload"></control>
|
||||
<control name="AutoCompleteExtender"></control>
|
||||
<control name="BalloonPopupExtender"></control>
|
||||
<control name="BarChart"></control>
|
||||
<control name="BubbleChart"></control>
|
||||
<control name="CalendarExtender"></control>
|
||||
<control name="CascadingDropDown"></control>
|
||||
<control name="CollapsiblePanelExtender"></control>
|
||||
<control name="ColorPickerExtender"></control>
|
||||
<control name="ComboBox"></control>
|
||||
<control name="ConfirmButtonExtender"></control>
|
||||
<control name="DragPanelExtender"></control>
|
||||
<control name="DropDownExtender"></control>
|
||||
<control name="DropShadowExtender"></control>
|
||||
<control name="DynamicPopulateExtender"></control>
|
||||
<control name="FilteredTextBoxExtender"></control>
|
||||
<control name="Gravatar"></control>
|
||||
<control name="HoverMenuExtender"></control>
|
||||
<control name="HtmlEditor.Editor"></control>
|
||||
<control name="HtmlEditorExtender"></control>
|
||||
<control name="LineChart"></control>
|
||||
<control name="ListSearchExtender"></control>
|
||||
<control name="MaskedEditExtender"></control>
|
||||
<control name="ModalPopupExtender"></control>
|
||||
<control name="MultiHandleSliderExtender"></control>
|
||||
<control name="MutuallyExclusiveCheckBoxExtender"></control>
|
||||
<control name="NoBotExtender"></control>
|
||||
<control name="NumericUpDownExtender"></control>
|
||||
<control name="PagingBulletedListExtender"></control>
|
||||
<control name="PasswordStrength"></control>
|
||||
<control name="PieChart"></control>
|
||||
<control name="PopupControlExtender"></control>
|
||||
<control name="RatingExtender"></control>
|
||||
<control name="ReorderList"></control>
|
||||
<control name="ResizableControlExtender"></control>
|
||||
<control name="RoundedCornersExtender"></control>
|
||||
<control name="Seadragon"></control>
|
||||
<control name="SliderExtender"></control>
|
||||
<control name="SlideShowExtender"></control>
|
||||
<control name="TabContainer"></control>
|
||||
<control name="TextBoxWatermarkExtender"></control>
|
||||
<control name="ToggleButtonExtender"></control>
|
||||
<control name="Twitter"></control>
|
||||
<control name="UpdatePanelAnimationExtender"></control>
|
||||
<control name="ValidatorCalloutExtender"></control>
|
||||
</controlBundle>
|
||||
</controlBundles>
|
||||
</ajaxControlToolkit>
|
||||
59
SIG.Web/AppCode/Configuration/AppServices.cs
Normal file
59
SIG.Web/AppCode/Configuration/AppServices.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using SIG.Web.AppCode.Contracts.Database;
|
||||
using SIG.Web.AppCode.Contracts.Search;
|
||||
using SIG.Web.AppCode.Contracts.Security;
|
||||
using SIG.Web.AppCode.Notifications.Contracts;
|
||||
using SIG.Web.AppCode.Notifications.Services;
|
||||
using SIG.Web.AppCode.Repositories.Security;
|
||||
using SIG.Web.AppCode.Services.Database;
|
||||
using SIG.Web.AppCode.Services.Menu;
|
||||
using SIG.Web.AppCode.Services.Search;
|
||||
using SIG.Web.AppCode.Services.Security;
|
||||
using SIG.Web.AppCode.Session;
|
||||
|
||||
namespace SIG.Web.AppCode.Configuration
|
||||
{
|
||||
public static class AppServices
|
||||
{
|
||||
public static IDbConnectionFactory DbConnectionFactory { get; private set; }
|
||||
public static IDbExecutor DbExecutor { get; private set; }
|
||||
public static IPasswordHasher PasswordHasher { get; private set; }
|
||||
public static IUserRepository UserRepository { get; private set; }
|
||||
public static IUserAdministrationRepository UserAdministrationRepository { get; private set; }
|
||||
public static IPublicRegistrationService PublicRegistrationService { get; private set; }
|
||||
public static ILoginAttemptRepository LoginAttemptRepository { get; private set; } public static IUserSessionRepository UserSessionRepository { get; private set; }
|
||||
public static ILoginService LoginService { get; private set; }
|
||||
public static IUserContextProvider UserContext { get; private set; }
|
||||
public static IPermissionService PermissionService { get; private set; }
|
||||
public static IMenuProvider MenuProvider { get; private set; }
|
||||
public static INotificationCenter NotificationCenter { get; private set; }
|
||||
public static IBuscadorCatalogoDataProvider SearchProvider { get; private set; }
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
DbConnectionFactory = new DbConnectionFactory();
|
||||
DbExecutor = new DbExecutor(DbConnectionFactory);
|
||||
|
||||
PasswordHasher = new PasswordHasher();
|
||||
|
||||
UserRepository = new UserRepository(DbExecutor);
|
||||
UserAdministrationRepository = new UserAdministrationRepository(DbExecutor);
|
||||
LoginAttemptRepository = new LoginAttemptRepository(DbExecutor); UserSessionRepository = new UserSessionRepository(DbExecutor);
|
||||
|
||||
UserContext = new WebFormsUserContextProvider();
|
||||
PermissionService = new PermissionService(DbExecutor);
|
||||
MenuProvider = new MenuProvider(DbExecutor);
|
||||
NotificationCenter = new DummyNotificationCenter();
|
||||
SearchProvider = new DummyBuscadorCatalogoDataProvider();
|
||||
|
||||
LoginService = new LoginService(
|
||||
UserRepository,
|
||||
PasswordHasher,
|
||||
LoginAttemptRepository,
|
||||
UserSessionRepository);
|
||||
|
||||
PublicRegistrationService = new PublicRegistrationService(
|
||||
UserAdministrationRepository,
|
||||
PasswordHasher,
|
||||
new SmtpEmailSender());
|
||||
} }
|
||||
}
|
||||
127
SIG.Web/AppCode/Configuration/BundleConfig.cs
Normal file
127
SIG.Web/AppCode/Configuration/BundleConfig.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System.Web.Optimization;
|
||||
using System.Web.UI;
|
||||
|
||||
namespace SIG.Web.AppCode.Configuration
|
||||
{
|
||||
public static class BundleConfig
|
||||
{
|
||||
public static void RegisterBundles(BundleCollection bundles)
|
||||
{
|
||||
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
|
||||
"~/Metronic/assets/global/plugins/jquery.min.js"
|
||||
));
|
||||
|
||||
ScriptManager.ScriptResourceMapping.AddDefinition("jquery", null, new ScriptResourceDefinition
|
||||
{
|
||||
Path = "~/bundles/jquery"
|
||||
});
|
||||
|
||||
bundles.Add(new ScriptBundle("~/bundles/scripts").Include(
|
||||
"~/Metronic/assets/global/plugins/jquery-ui/jquery-ui.min.js",
|
||||
"~/Metronic/assets/global/plugins/jquery-migrate.min.js",
|
||||
"~/Scripts/funciones.js",
|
||||
"~/Scripts/FixFocus.js",
|
||||
"~/Scripts/jquery.number.js",
|
||||
"~/Scripts/jquery.maskMoney.js",
|
||||
"~/Scripts/jquery.thickbox.js",
|
||||
"~/Scripts/jquery.fancybox-1.3.4.js",
|
||||
"~/Scripts/jquery.maskedinput-1.2.2.min.js",
|
||||
"~/Scripts/raphael.2.1.0.min.js",
|
||||
"~/Scripts/justgage.1.0.1.min.js",
|
||||
"~/Scripts/multiselect.js",
|
||||
"~/Scripts/jquery.mask.min.js",
|
||||
"~/Scripts/gridviewScroll.js",
|
||||
"~/Metronic/Carusel/slick.min.js"
|
||||
));
|
||||
|
||||
bundles.Add(new ScriptBundle("~/Metronic/assets/global/scripts/JSV1").Include(
|
||||
"~/Metronic/assets/global/plugins/bootstrap/js/bootstrap.min.js",
|
||||
"~/Metronic/assets/global/plugins/bootstrap-hover-dropdown/bootstrap-hover-dropdown.min.js",
|
||||
"~/Metronic/assets/global/plugins/jquery-slimscroll/jquery.slimscroll.min.js",
|
||||
"~/Metronic/assets/global/plugins/jquery.blockui.min.js",
|
||||
"~/Metronic/assets/global/plugins/jquery.cokie.min.js",
|
||||
"~/Metronic/assets/global/plugins/bootstrap-switch/js/bootstrap-switch.min.js",
|
||||
"~/Metronic/assets/global/plugins/select2/select2.full.min.js",
|
||||
"~/Metronic/assets/global/plugins/select2/i18n/es.js",
|
||||
"~/Metronic/assets/global/plugins/bootstrap-confirmation/bootstrap-confirmation.min.js",
|
||||
"~/Metronic/assets/global/plugins/bootbox/bootbox.min.js",
|
||||
"~/Metronic/assets/global/plugins/bootstrap-toastr/toastr.min.js",
|
||||
"~/Metronic/assets/global/scripts/metronic.js",
|
||||
"~/Metronic/assets/admin/layout/scripts/layout.js",
|
||||
"~/Metronic/assets/admin/layout/scripts/quick-sidebar.js",
|
||||
"~/Metronic/assets/admin/layout/scripts/demo.js",
|
||||
"~/Scripts/porcentaje-box.js",
|
||||
"~/Scripts/DropdownGrid.js",
|
||||
"~/Scripts/UcBuscadorCatalogo.js",
|
||||
"~/Scripts/SigPdfViewer.js",
|
||||
"~/Scripts/SigPdfDrawer.js",
|
||||
"~/Scripts/custom.js"
|
||||
));
|
||||
|
||||
bundles.Add(new ScriptBundle("~/bundles/WebFormsJs").Include(
|
||||
"~/Scripts/WebForms/WebForms.js",
|
||||
"~/Scripts/WebForms/WebUIValidation.js",
|
||||
"~/Scripts/WebForms/MenuStandards.js",
|
||||
"~/Scripts/WebForms/Focus.js",
|
||||
"~/Scripts/WebForms/GridView.js",
|
||||
"~/Scripts/WebForms/DetailsView.js",
|
||||
"~/Scripts/WebForms/TreeView.js",
|
||||
"~/Scripts/WebForms/WebParts.js"
|
||||
));
|
||||
|
||||
ScriptManager.ScriptResourceMapping.AddDefinition("WebFormsBundle", new ScriptResourceDefinition
|
||||
{
|
||||
Path = "~/bundles/WebFormsJs"
|
||||
});
|
||||
|
||||
bundles.Add(new ScriptBundle("~/bundles/MsAjaxJs").Include(
|
||||
"~/Scripts/WebForms/MsAjax/MicrosoftAjax.js",
|
||||
"~/Scripts/WebForms/MsAjax/MicrosoftAjaxApplicationServices.js",
|
||||
"~/Scripts/WebForms/MsAjax/MicrosoftAjaxTimer.js",
|
||||
"~/Scripts/WebForms/MsAjax/MicrosoftAjaxWebForms.js"
|
||||
));
|
||||
|
||||
ScriptManager.ScriptResourceMapping.AddDefinition("MsAjaxBundle", new ScriptResourceDefinition
|
||||
{
|
||||
Path = "~/bundles/MsAjaxJs"
|
||||
});
|
||||
|
||||
bundles.Add(new ScriptBundle("~/bundles/modernizr").Include("~/Scripts/modernizr-*"));
|
||||
|
||||
ScriptManager.ScriptResourceMapping.AddDefinition("respond", new ScriptResourceDefinition
|
||||
{
|
||||
Path = "~/Scripts/respond.min.js",
|
||||
DebugPath = "~/Scripts/respond.js"
|
||||
});
|
||||
|
||||
bundles.Add(new StyleBundle("~/Content/AjaxControlToolkit/Styles/Bundle").Include(
|
||||
"~/Content/AjaxControlToolkit/Styles/AjaxFileUpload.min.css",
|
||||
"~/Content/AjaxControlToolkit/Styles/Calendar.min.css",
|
||||
"~/Content/AjaxControlToolkit/Styles/Tabs.min.css",
|
||||
"~/Content/AjaxControlToolkit/Styles/ValidatorCallout.min.css",
|
||||
"~/Content/AjaxControlToolkit/Styles/HtmlEditorExtender.min.css"
|
||||
));
|
||||
|
||||
bundles.Add(new StyleBundle("~/Metronic/assets/global/css/cssV1")
|
||||
.Include("~/Metronic/assets/global/plugins/font-awesome/css/font-awesome.min.css", new CssRewriteUrlTransform())
|
||||
.Include("~/Metronic/assets/global/plugins/bootstrap/css/bootstrap.min.css", new CssRewriteUrlTransform())
|
||||
.Include("~/Metronic/assets/global/plugins/simple-line-icons/simple-line-icons.min.css", new CssRewriteUrlTransform())
|
||||
.Include(
|
||||
"~/Styles/porcentaje-box.css",
|
||||
"~/Styles/DropdownGrid.css",
|
||||
"~/Styles/UcBuscadorCatalogo.css",
|
||||
"~/Styles/SigPdfViewer.css",
|
||||
"~/Styles/SigPdfDrawer.css",
|
||||
"~/Styles/Estilos2.css",
|
||||
"~/Metronic/assets/global/plugins/bootstrap-switch/css/bootstrap-switch.min.css",
|
||||
"~/Metronic/assets/global/plugins/bootstrap-toastr/toastr.min.css",
|
||||
"~/Metronic/assets/global/plugins/select2/select2.css",
|
||||
"~/Metronic/assets/global/css/components.css",
|
||||
"~/Metronic/assets/global/css/plugins.css",
|
||||
"~/Metronic/assets/admin/layout/css/layout.css",
|
||||
"~/Metronic/assets/admin/layout/css/custom.css")
|
||||
.Include("~/Metronic/Carusel/slick.css", new CssRewriteUrlTransform())
|
||||
.Include("~/Metronic/Carusel/slick-theme.css", new CssRewriteUrlTransform()));
|
||||
}
|
||||
}
|
||||
}
|
||||
294
SIG.Web/AppCode/Configuration/ErrorHandlerConfig.cs
Normal file
294
SIG.Web/AppCode/Configuration/ErrorHandlerConfig.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using SIG.Web.AppCode.Services.Errors;
|
||||
using SIG.Web.AppCode.Session;
|
||||
|
||||
namespace SIG.Web.AppCode.Configuration
|
||||
{
|
||||
public static class ErrorHandlerConfig
|
||||
{
|
||||
public static void Register()
|
||||
{
|
||||
}
|
||||
|
||||
public static void HandleServerError(
|
||||
Exception exception,
|
||||
HttpContext context)
|
||||
{
|
||||
if (exception == null || context == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
UserSessionModel usuario = ObtenerUsuarioActual();
|
||||
|
||||
string cveUsuario = usuario != null
|
||||
? usuario.CveUsuario.ToString()
|
||||
: string.Empty;
|
||||
|
||||
string nombreUsuario = usuario != null
|
||||
? usuario.Nombre
|
||||
: string.Empty;
|
||||
|
||||
int cveRole = usuario != null
|
||||
? usuario.CveRol
|
||||
: 0;
|
||||
|
||||
string codigo = SIGErrorManager.Registrar(
|
||||
exception,
|
||||
context,
|
||||
"Application_Error",
|
||||
cveUsuario,
|
||||
nombreUsuario
|
||||
);
|
||||
|
||||
bool esPeticionAsincrona =
|
||||
SIGErrorManager.EsPeticionAsincrona(context);
|
||||
|
||||
/*
|
||||
* El detalle t<>cnico completo siempre se guarda en log.
|
||||
* Solo el administrador puede verlo en la respuesta.
|
||||
*/
|
||||
bool mostrarDetalle = cveRole == 1;
|
||||
|
||||
context.Server.ClearError();
|
||||
|
||||
if (esPeticionAsincrona)
|
||||
{
|
||||
EscribirErrorAsincrono(
|
||||
context,
|
||||
codigo,
|
||||
exception,
|
||||
mostrarDetalle
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
EscribirPaginaError(
|
||||
context,
|
||||
codigo,
|
||||
exception,
|
||||
mostrarDetalle,
|
||||
cveUsuario,
|
||||
nombreUsuario,
|
||||
cveRole
|
||||
);
|
||||
}
|
||||
catch (Exception handlerException)
|
||||
{
|
||||
try
|
||||
{
|
||||
SIGErrorManager.Registrar(
|
||||
handlerException,
|
||||
context,
|
||||
"ErrorHandlerConfig.HandleServerError",
|
||||
string.Empty,
|
||||
string.Empty
|
||||
);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// El manejador global nunca debe provocar otro error.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
context.Server.ClearError();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFriendlyMessage(
|
||||
Exception exception)
|
||||
{
|
||||
if (exception == null)
|
||||
{
|
||||
return "Ocurri<72> un error inesperado.";
|
||||
}
|
||||
|
||||
Exception baseException =
|
||||
exception.GetBaseException();
|
||||
|
||||
SqlException sqlException =
|
||||
baseException as SqlException;
|
||||
|
||||
if (sqlException != null)
|
||||
{
|
||||
switch (sqlException.Number)
|
||||
{
|
||||
case 207:
|
||||
return "No fue posible completar la operaci<63>n porque falta una columna requerida en la base de datos.";
|
||||
|
||||
case 208:
|
||||
return "No fue posible completar la operaci<63>n porque falta una tabla o vista requerida en la base de datos.";
|
||||
|
||||
case 4060:
|
||||
return "No fue posible abrir la base de datos configurada.";
|
||||
|
||||
case 18456:
|
||||
return "No fue posible iniciar sesi<73>n en la base de datos.";
|
||||
|
||||
case -2:
|
||||
return "La base de datos tard<72> demasiado en responder.";
|
||||
|
||||
case 1205:
|
||||
return "La operaci<63>n no pudo completarse porque ocurri<72> un bloqueo en la base de datos. Intente nuevamente.";
|
||||
|
||||
case 547:
|
||||
return "No fue posible completar la operaci<63>n porque existen registros relacionados.";
|
||||
|
||||
case 2627:
|
||||
case 2601:
|
||||
return "Ya existe un registro con la misma informaci<63>n.";
|
||||
|
||||
default:
|
||||
return "Ocurri<72> un error al consultar la base de datos.";
|
||||
}
|
||||
}
|
||||
|
||||
if (baseException is TimeoutException)
|
||||
{
|
||||
return "La solicitud tard<72> demasiado en responder. Intente nuevamente.";
|
||||
}
|
||||
|
||||
if (baseException is OperationCanceledException)
|
||||
{
|
||||
return "La operaci<63>n fue cancelada antes de completarse.";
|
||||
}
|
||||
|
||||
HttpException httpException =
|
||||
baseException as HttpException;
|
||||
|
||||
if (httpException != null)
|
||||
{
|
||||
int statusCode =
|
||||
httpException.GetHttpCode();
|
||||
|
||||
switch (statusCode)
|
||||
{
|
||||
case 400:
|
||||
return "La solicitud enviada no es v<>lida.";
|
||||
|
||||
case 401:
|
||||
return "Su sesi<73>n no tiene autorizaci<63>n para realizar esta operaci<63>n.";
|
||||
|
||||
case 403:
|
||||
return "No tiene permisos para realizar esta operaci<63>n.";
|
||||
|
||||
case 404:
|
||||
return "No se encontr<74> el recurso solicitado.";
|
||||
|
||||
case 408:
|
||||
return "La solicitud tard<72> demasiado en responder.";
|
||||
|
||||
case 413:
|
||||
return "El archivo o contenido enviado supera el tama<6D>o permitido.";
|
||||
|
||||
default:
|
||||
return "Ocurri<72> un error al procesar la solicitud.";
|
||||
}
|
||||
}
|
||||
|
||||
return "Ocurri<72> un error inesperado. Intente nuevamente.";
|
||||
}
|
||||
|
||||
private static void EscribirErrorAsincrono(
|
||||
HttpContext context,
|
||||
string codigo,
|
||||
Exception exception,
|
||||
bool mostrarDetalle)
|
||||
{
|
||||
string respuesta =
|
||||
"SIG_ERROR|" + codigo;
|
||||
|
||||
if (mostrarDetalle)
|
||||
{
|
||||
string detalle =
|
||||
SIGErrorManager.ObtenerDetalleTecnico(
|
||||
exception
|
||||
);
|
||||
|
||||
string detalleBase64 =
|
||||
Convert.ToBase64String(
|
||||
Encoding.UTF8.GetBytes(detalle)
|
||||
);
|
||||
|
||||
respuesta += "|" + detalleBase64;
|
||||
}
|
||||
|
||||
context.Response.Clear();
|
||||
context.Response.ClearHeaders();
|
||||
|
||||
context.Response.StatusCode = 500;
|
||||
context.Response.TrySkipIisCustomErrors = true;
|
||||
context.Response.ContentType =
|
||||
"text/plain; charset=utf-8";
|
||||
|
||||
context.Response.AddHeader(
|
||||
"X-SIG-Error-Code",
|
||||
codigo
|
||||
);
|
||||
|
||||
context.Response.Write(respuesta);
|
||||
|
||||
context.ApplicationInstance.CompleteRequest();
|
||||
}
|
||||
|
||||
private static void EscribirPaginaError(
|
||||
HttpContext context,
|
||||
string codigo,
|
||||
Exception exception,
|
||||
bool mostrarDetalle,
|
||||
string cveUsuario,
|
||||
string nombreUsuario,
|
||||
int cveRole)
|
||||
{
|
||||
string html =
|
||||
SIGErrorManager.CrearHtmlError(
|
||||
codigo,
|
||||
exception,
|
||||
mostrarDetalle,
|
||||
cveUsuario,
|
||||
nombreUsuario,
|
||||
cveRole
|
||||
);
|
||||
|
||||
context.Response.Clear();
|
||||
context.Response.ClearHeaders();
|
||||
|
||||
context.Response.StatusCode = 500;
|
||||
context.Response.TrySkipIisCustomErrors = true;
|
||||
context.Response.ContentType =
|
||||
"text/html; charset=utf-8";
|
||||
|
||||
context.Response.Write(html);
|
||||
|
||||
context.ApplicationInstance.CompleteRequest();
|
||||
}
|
||||
|
||||
private static UserSessionModel ObtenerUsuarioActual()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (AppServices.UserContext == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return AppServices.UserContext.GetCurrentUser();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
SIG.Web/AppCode/Contracts/Data/DbProviderKind.cs
Normal file
8
SIG.Web/AppCode/Contracts/Data/DbProviderKind.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace SIG.Web.AppCode.Contracts.Data
|
||||
{
|
||||
public enum DbProviderKind
|
||||
{
|
||||
SqlServer,
|
||||
PostgreSql
|
||||
}
|
||||
}
|
||||
10
SIG.Web/AppCode/Contracts/Data/DomainConnectionSettings.cs
Normal file
10
SIG.Web/AppCode/Contracts/Data/DomainConnectionSettings.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace SIG.Web.AppCode.Contracts.Data
|
||||
{
|
||||
public sealed class DomainConnectionSettings
|
||||
{
|
||||
public string Domain { get; set; }
|
||||
public DbProviderKind Provider { get; set; }
|
||||
public string ConnectionStringName { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
}
|
||||
}
|
||||
9
SIG.Web/AppCode/Contracts/Data/IDbConnectionFactory.cs
Normal file
9
SIG.Web/AppCode/Contracts/Data/IDbConnectionFactory.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Data;
|
||||
|
||||
namespace SIG.Web.AppCode.Contracts.Data
|
||||
{
|
||||
public interface IDbConnectionFactory
|
||||
{
|
||||
IDbConnection Create(string domain);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace SIG.Web.AppCode.Contracts.Data
|
||||
{
|
||||
public interface IDomainConnectionResolver
|
||||
{
|
||||
DomainConnectionSettings Resolve(string domain);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using System.Data;
|
||||
namespace SIG.Web.AppCode.Contracts.Database
|
||||
{
|
||||
public interface IDbConnectionFactory { IDbConnection CreateConnection(string domainName); }
|
||||
}
|
||||
11
SIG.Web/AppCode/Contracts/Database/IDbExecutor.cs
Normal file
11
SIG.Web/AppCode/Contracts/Database/IDbExecutor.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using SIG.Web.AppCode.Models.Common;
|
||||
|
||||
namespace SIG.Web.AppCode.Contracts.Database
|
||||
{
|
||||
public interface IDbExecutor
|
||||
{
|
||||
DbResult<T> Execute<T>(string domainName, Func<IDbConnection, T> action);
|
||||
}
|
||||
}
|
||||
7
SIG.Web/AppCode/Contracts/Login/ILoginService.cs
Normal file
7
SIG.Web/AppCode/Contracts/Login/ILoginService.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace SIG.Web.AppCode.Contracts.Login
|
||||
{
|
||||
public interface ILoginService
|
||||
{
|
||||
LoginResult Login(string userName, string password, string domain);
|
||||
}
|
||||
}
|
||||
21
SIG.Web/AppCode/Contracts/Login/LoginResult.cs
Normal file
21
SIG.Web/AppCode/Contracts/Login/LoginResult.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using SIG.Web.AppCode.Session;
|
||||
|
||||
namespace SIG.Web.AppCode.Contracts.Login
|
||||
{
|
||||
public sealed class LoginResult
|
||||
{
|
||||
public bool Exitoso { get; private set; }
|
||||
public string Mensaje { get; private set; }
|
||||
public UserSessionModel Usuario { get; private set; }
|
||||
|
||||
public static LoginResult Ok(UserSessionModel usuario)
|
||||
{
|
||||
return new LoginResult { Exitoso = true, Usuario = usuario, Mensaje = "OK" };
|
||||
}
|
||||
|
||||
public static LoginResult Fail(string mensaje)
|
||||
{
|
||||
return new LoginResult { Exitoso = false, Mensaje = mensaje };
|
||||
}
|
||||
}
|
||||
}
|
||||
45
SIG.Web/AppCode/Contracts/Search/BuscadorCatalogoModels.cs
Normal file
45
SIG.Web/AppCode/Contracts/Search/BuscadorCatalogoModels.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SIG.Web.AppCode.Contracts.Search
|
||||
{
|
||||
public sealed class BuscadorCatalogoFiltro
|
||||
{
|
||||
public string Campo { get; set; }
|
||||
public string Operador { get; set; }
|
||||
public string Valor { get; set; }
|
||||
}
|
||||
|
||||
public sealed class BuscadorCatalogoOrden
|
||||
{
|
||||
public string Campo { get; set; }
|
||||
public string Direccion { get; set; } = "ASC";
|
||||
}
|
||||
|
||||
public sealed class BuscadorCatalogoColumna
|
||||
{
|
||||
public string Campo { get; set; }
|
||||
public string Titulo { get; set; }
|
||||
public bool Visible { get; set; } = true;
|
||||
public bool Busqueda { get; set; } = true;
|
||||
public string CssClass { get; set; }
|
||||
}
|
||||
|
||||
public sealed class BuscadorCatalogoCallbackSolicitud
|
||||
{
|
||||
public string Catalogo { get; set; }
|
||||
public string Termino { get; set; }
|
||||
public int Pagina { get; set; } = 1;
|
||||
public int TamanoPagina { get; set; } = 20;
|
||||
public List<BuscadorCatalogoFiltro> Filtros { get; set; } = new List<BuscadorCatalogoFiltro>();
|
||||
public List<BuscadorCatalogoOrden> Orden { get; set; } = new List<BuscadorCatalogoOrden>();
|
||||
}
|
||||
|
||||
public sealed class BuscadorCatalogoCallbackRespuesta
|
||||
{
|
||||
public bool Success { get; set; } = true;
|
||||
public string Message { get; set; }
|
||||
public int Total { get; set; }
|
||||
public List<Dictionary<string, object>> Rows { get; set; } = new List<Dictionary<string, object>>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using SIG.Web.AppCode.Session;
|
||||
|
||||
namespace SIG.Web.AppCode.Contracts.Search
|
||||
{
|
||||
public interface IBuscadorCatalogoDataProvider
|
||||
{
|
||||
BuscadorCatalogoCallbackRespuesta Search(BuscadorCatalogoCallbackSolicitud request, UserSessionModel user);
|
||||
}
|
||||
}
|
||||
8
SIG.Web/AppCode/Contracts/Security/IEmailSender.cs
Normal file
8
SIG.Web/AppCode/Contracts/Security/IEmailSender.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace SIG.Web.AppCode.Contracts.Security
|
||||
{
|
||||
public interface IEmailSender
|
||||
{
|
||||
bool IsConfigured { get; }
|
||||
void SendVerification(string recipient, string verificationUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using SIG.Web.AppCode.Models.Common;
|
||||
|
||||
namespace SIG.Web.AppCode.Contracts.Security
|
||||
{
|
||||
public interface ILoginAttemptRepository
|
||||
{
|
||||
DbResult<bool> Save(int? cveUser, string username, bool wasSuccessful, string failureReason, string ipAddress, string userAgent, string domainName);
|
||||
}
|
||||
}
|
||||
9
SIG.Web/AppCode/Contracts/Security/ILoginService.cs
Normal file
9
SIG.Web/AppCode/Contracts/Security/ILoginService.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using SIG.Web.AppCode.Models.Security;
|
||||
namespace SIG.Web.AppCode.Contracts.Security
|
||||
{
|
||||
public interface ILoginService
|
||||
{
|
||||
LoginResult Login(LoginRequest request);
|
||||
void Logout(string sessionToken, string domainName);
|
||||
}
|
||||
}
|
||||
8
SIG.Web/AppCode/Contracts/Security/IPasswordHasher.cs
Normal file
8
SIG.Web/AppCode/Contracts/Security/IPasswordHasher.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace SIG.Web.AppCode.Contracts.Security
|
||||
{
|
||||
public interface IPasswordHasher
|
||||
{
|
||||
string HashPassword(string password);
|
||||
bool VerifyPassword(string password, string storedHash);
|
||||
}
|
||||
}
|
||||
8
SIG.Web/AppCode/Contracts/Security/IPermissionService.cs
Normal file
8
SIG.Web/AppCode/Contracts/Security/IPermissionService.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace SIG.Web.AppCode.Contracts.Security
|
||||
{
|
||||
public interface IPermissionService
|
||||
{
|
||||
bool HasPermission(int cveUser, string permissionCode);
|
||||
bool CanAccessPage(int cveUser, string pageUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using SIG.Web.AppCode.Models.Common;
|
||||
using SIG.Web.AppCode.Models.Security;
|
||||
|
||||
namespace SIG.Web.AppCode.Contracts.Security
|
||||
{
|
||||
public interface IPublicRegistrationService
|
||||
{
|
||||
DbResult<bool> Validate(PublicRegistrationDraft draft, string domainName);
|
||||
DbResult<List<StateOptionModel>> ListStates(string domainName);
|
||||
DbResult<bool> Register(PublicRegistrationDraft draft, string verificationBaseUrl, string domainName);
|
||||
DbResult<bool> Verify(string token, string domainName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using SIG.Web.AppCode.Models.Common;
|
||||
using SIG.Web.AppCode.Models.Security;
|
||||
|
||||
namespace SIG.Web.AppCode.Contracts.Security
|
||||
{
|
||||
public interface IUserAdministrationRepository
|
||||
{
|
||||
DbResult<List<UserAdministrationModel>> ListUsers(string text, int executingUser, string domainName);
|
||||
DbResult<UserAdministrationModel> GetUser(int userId, int executingUser, string domainName);
|
||||
DbResult<List<RoleOptionModel>> ListRoles(int executingUser, string domainName);
|
||||
DbResult<List<StateOptionModel>> ListStates(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);
|
||||
DbResult<bool> SaveUserClient(int userId, int clientId, int order, bool primary, int executingUser, string domainName);
|
||||
DbResult<bool> ValidateRegistration(string username, string email, string rfc, string domainName);
|
||||
DbResult<RegistrationCreatedModel> CreateRegistration(PublicRegistrationDraft draft, string passwordHash, string tokenHash, string domainName);
|
||||
DbResult<bool> VerifyEmail(string tokenHash, string domainName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using SIG.Web.AppCode.Session;
|
||||
namespace SIG.Web.AppCode.Contracts.Security
|
||||
{
|
||||
public interface IUserContextProvider
|
||||
{
|
||||
bool IsAuthenticated(); UserSessionModel GetCurrentUser(); void SetCurrentUser(UserSessionModel user); void Clear();
|
||||
}
|
||||
}
|
||||
12
SIG.Web/AppCode/Contracts/Security/IUserRepository.cs
Normal file
12
SIG.Web/AppCode/Contracts/Security/IUserRepository.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using SIG.Web.AppCode.Models.Common;
|
||||
using SIG.Web.AppCode.Models.Security;
|
||||
|
||||
namespace SIG.Web.AppCode.Contracts.Security
|
||||
{
|
||||
public interface IUserRepository
|
||||
{
|
||||
DbResult<UserModel> ObtenerPorNombreUsuario(string username, string domainName);
|
||||
DbResult<bool> RegisterSuccessfulLogin(int cveUser, string domainName);
|
||||
DbResult<bool> RegisterFailedLogin(int cveUser, string domainName);
|
||||
}
|
||||
}
|
||||
10
SIG.Web/AppCode/Contracts/Security/IUserSessionRepository.cs
Normal file
10
SIG.Web/AppCode/Contracts/Security/IUserSessionRepository.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using SIG.Web.AppCode.Models.Common;
|
||||
|
||||
namespace SIG.Web.AppCode.Contracts.Security
|
||||
{
|
||||
public interface IUserSessionRepository
|
||||
{
|
||||
DbResult<bool> StartSession(int cveUser, string sessionToken, string ipAddress, string userAgent, string domainName);
|
||||
DbResult<bool> EndSession(string sessionToken, string domainName);
|
||||
}
|
||||
}
|
||||
37
SIG.Web/AppCode/Models/Common/DbResult.cs
Normal file
37
SIG.Web/AppCode/Models/Common/DbResult.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace SIG.Web.AppCode.Models.Common
|
||||
{
|
||||
public sealed class DbResult<T>
|
||||
{
|
||||
public bool Success { get; private set; }
|
||||
public T Data { get; private set; }
|
||||
public string UserMessage { get; private set; }
|
||||
public string TechnicalMessage { get; private set; }
|
||||
public Exception Exception { get; private set; }
|
||||
|
||||
public static DbResult<T> Ok(T data)
|
||||
{
|
||||
return new DbResult<T>
|
||||
{
|
||||
Success = true,
|
||||
Data = data,
|
||||
UserMessage = string.Empty,
|
||||
TechnicalMessage = string.Empty,
|
||||
Exception = null
|
||||
};
|
||||
}
|
||||
|
||||
public static DbResult<T> Fail(string userMessage, string technicalMessage, Exception exception)
|
||||
{
|
||||
return new DbResult<T>
|
||||
{
|
||||
Success = false,
|
||||
Data = default(T),
|
||||
UserMessage = userMessage,
|
||||
TechnicalMessage = technicalMessage,
|
||||
Exception = exception
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
9
SIG.Web/AppCode/Models/Common/StoredProcedureResult.cs
Normal file
9
SIG.Web/AppCode/Models/Common/StoredProcedureResult.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace SIG.Web.AppCode.Models.Common
|
||||
{
|
||||
public sealed class StoredProcedureResult
|
||||
{
|
||||
public bool Return { get; set; }
|
||||
public int ErrorTipo { get; set; }
|
||||
public string ErrorMostrar { get; set; }
|
||||
}
|
||||
}
|
||||
11
SIG.Web/AppCode/Models/Security/LoginRequest.cs
Normal file
11
SIG.Web/AppCode/Models/Security/LoginRequest.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace SIG.Web.AppCode.Models.Security
|
||||
{
|
||||
public sealed class LoginRequest
|
||||
{
|
||||
public string NombreUsuario { get; set; }
|
||||
public string Contrasena { get; set; }
|
||||
public string NombreDominio { get; set; }
|
||||
public string DireccionIP { get; set; }
|
||||
public string AgenteUsuario { get; set; }
|
||||
}
|
||||
}
|
||||
12
SIG.Web/AppCode/Models/Security/LoginResult.cs
Normal file
12
SIG.Web/AppCode/Models/Security/LoginResult.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using SIG.Web.AppCode.Session;
|
||||
namespace SIG.Web.AppCode.Models.Security
|
||||
{
|
||||
public sealed class LoginResult
|
||||
{
|
||||
public bool Exitoso { get; private set; }
|
||||
public string Mensaje { get; private set; }
|
||||
public UserSessionModel Usuario { get; private set; }
|
||||
public static LoginResult Ok(UserSessionModel usuario) { return new LoginResult { Exitoso = true, Usuario = usuario, Mensaje = string.Empty }; }
|
||||
public static LoginResult Fail(string mensaje) { return new LoginResult { Exitoso = false, Mensaje = mensaje }; }
|
||||
}
|
||||
}
|
||||
70
SIG.Web/AppCode/Models/Security/UserAdministrationModels.cs
Normal file
70
SIG.Web/AppCode/Models/Security/UserAdministrationModels.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
|
||||
namespace SIG.Web.AppCode.Models.Security
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class PublicRegistrationDraft
|
||||
{
|
||||
public string NombreUsuario { get; set; }
|
||||
public string CorreoElectronico { get; set; }
|
||||
public string Contrasena { get; set; }
|
||||
public string RFC { get; set; }
|
||||
public string RazonSocial { get; set; }
|
||||
public string NombreComercial { get; set; }
|
||||
public string Direccion { get; set; }
|
||||
public string NoExt { get; set; }
|
||||
public string NoInt { get; set; }
|
||||
public string Colonia { get; set; }
|
||||
public string Ciudad { get; set; }
|
||||
public int CveSATEstado { get; set; }
|
||||
public string Pais { get; set; }
|
||||
public string CP { get; set; }
|
||||
public string Telefono { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UserAdministrationModel
|
||||
{
|
||||
public int CveUsuario { get; set; }
|
||||
public string NombreUsuario { get; set; }
|
||||
public string CorreoElectronico { get; set; }
|
||||
public int CveRol { get; set; }
|
||||
public string NombreRol { get; set; }
|
||||
public bool Activo { get; set; }
|
||||
public bool DebeCambiarContrasena { get; set; }
|
||||
public bool CorreoVerificado { get; set; }
|
||||
public DateTime FechaCreacion { get; set; }
|
||||
}
|
||||
|
||||
public sealed class RoleOptionModel
|
||||
{
|
||||
public int CveRol { get; set; }
|
||||
public string NombreRol { get; set; }
|
||||
public string Descripcion { get; set; }
|
||||
}
|
||||
|
||||
public sealed class StateOptionModel
|
||||
{
|
||||
public int CveEstado { get; set; }
|
||||
public string ClaveEstado { get; set; }
|
||||
public string NombreEstado { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UserClientModel
|
||||
{
|
||||
public int CveUsuarioCliente { get; set; }
|
||||
public int CveUsuario { get; set; }
|
||||
public int CveCliente { get; set; }
|
||||
public string Nombre { get; set; }
|
||||
public string RFC { get; set; }
|
||||
public int Orden { get; set; }
|
||||
public bool EsPrincipal { get; set; }
|
||||
public bool Activo { get; set; }
|
||||
public bool ClienteActivo { get; set; }
|
||||
}
|
||||
|
||||
public sealed class RegistrationCreatedModel
|
||||
{
|
||||
public int CveUsuario { get; set; }
|
||||
public int CveCliente { get; set; }
|
||||
}
|
||||
}
|
||||
19
SIG.Web/AppCode/Models/Security/UserModel.cs
Normal file
19
SIG.Web/AppCode/Models/Security/UserModel.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
namespace SIG.Web.AppCode.Models.Security
|
||||
{
|
||||
public sealed class UserModel
|
||||
{
|
||||
public int CveUsuario { get; set; }
|
||||
public string NombreUsuario { get; set; }
|
||||
public string HashContrasena { get; set; }
|
||||
public string CorreoElectronico { get; set; }
|
||||
public int CveRol { get; set; }
|
||||
public string NombreRol { get; set; }
|
||||
public bool Activo { get; set; }
|
||||
public bool CorreoVerificado { get; set; }
|
||||
public bool DebeCambiarContrasena { get; set; } public int IntentosFallidosInicioSesion { get; set; }
|
||||
public DateTime? BloqueadoHasta { get; set; }
|
||||
public DateTime? FechaUltimoInicioSesion { get; set; }
|
||||
public DateTime? FechaUltimaConexion { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using SIG.Web.AppCode.Notifications.Models;
|
||||
|
||||
namespace SIG.Web.AppCode.Notifications.Contracts
|
||||
{
|
||||
public interface INotificationCenter
|
||||
{
|
||||
void Queue(NotificationMessage message);
|
||||
void MarkAsShown(int notificationId, int userId);
|
||||
IReadOnlyList<NotificationMessage> GetBellMessages(int userId);
|
||||
int CountPending(int userId);
|
||||
}
|
||||
}
|
||||
12
SIG.Web/AppCode/Notifications/Hubs/NotificationHub.cs
Normal file
12
SIG.Web/AppCode/Notifications/Hubs/NotificationHub.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNet.SignalR;
|
||||
|
||||
namespace SIG.Web.AppCode.Notifications.Hubs
|
||||
{
|
||||
public class NotificationHub : Hub
|
||||
{
|
||||
public void Ping()
|
||||
{
|
||||
Clients.Caller.pong("SignalR is working.");
|
||||
}
|
||||
}
|
||||
}
|
||||
22
SIG.Web/AppCode/Notifications/Models/NotificationMessage.cs
Normal file
22
SIG.Web/AppCode/Notifications/Models/NotificationMessage.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace SIG.Web.AppCode.Notifications.Models
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class NotificationMessage
|
||||
{
|
||||
public int CveNotificacion { get; set; }
|
||||
public int CveUsuario { get; set; }
|
||||
public string TipoNotificacion { get; set; }
|
||||
public string EstadoNotificacion { get; set; }
|
||||
public string Titulo { get; set; }
|
||||
public string Mensaje { get; set; }
|
||||
public string Url { get; set; }
|
||||
public bool Leida { get; set; }
|
||||
public bool MostradaEnCampana { get; set; }
|
||||
public DateTime? FechaEnvio { get; set; }
|
||||
public DateTime? FechaLectura { get; set; }
|
||||
public DateTime FechaCreacion { get; set; }
|
||||
public DateTime? FechaActualizacion { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SIG.Web.AppCode.Notifications.Contracts;
|
||||
using SIG.Web.AppCode.Notifications.Models;
|
||||
|
||||
namespace SIG.Web.AppCode.Notifications.Services
|
||||
{
|
||||
public sealed class DummyNotificationCenter : INotificationCenter
|
||||
{
|
||||
private static readonly List<NotificationMessage> Items = new List<NotificationMessage>();
|
||||
|
||||
public void Queue(NotificationMessage message)
|
||||
{
|
||||
if (message == null) return;
|
||||
message.CveNotificacion = Items.Count + 1;
|
||||
message.FechaCreacion = System.DateTime.Now;
|
||||
message.EstadoNotificacion = "Pendiente";
|
||||
Items.Add(message);
|
||||
}
|
||||
|
||||
public void MarkAsShown(int notificationId, int userId)
|
||||
{
|
||||
var item = Items.FirstOrDefault(x => x.CveNotificacion == notificationId && x.CveUsuario == userId);
|
||||
if (item == null) return;
|
||||
item.MostradaEnCampana = true;
|
||||
}
|
||||
|
||||
public IReadOnlyList<NotificationMessage> GetBellMessages(int userId)
|
||||
{
|
||||
return Items.Where(x => x.CveUsuario == userId && !x.Leida).ToList();
|
||||
}
|
||||
|
||||
public int CountPending(int userId)
|
||||
{
|
||||
return GetBellMessages(userId).Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Data;
|
||||
using SIG.Web.AppCode.Contracts.Database;
|
||||
using SIG.Web.AppCode.Contracts.Security;
|
||||
using SIG.Web.AppCode.Models.Common;
|
||||
using SIG.Web.AppCode.Services.Database;
|
||||
|
||||
namespace SIG.Web.AppCode.Repositories.Security
|
||||
{
|
||||
public sealed class LoginAttemptRepository : ILoginAttemptRepository
|
||||
{
|
||||
private readonly IDbExecutor _dbExecutor;
|
||||
|
||||
public LoginAttemptRepository(IDbExecutor dbExecutor)
|
||||
{
|
||||
_dbExecutor = dbExecutor;
|
||||
}
|
||||
|
||||
public DbResult<bool> Save(int? cveUser, string username, bool wasSuccessful, string failureReason, string ipAddress, string userAgent, string domainName)
|
||||
{
|
||||
return _dbExecutor.Execute(domainName, connection =>
|
||||
{
|
||||
using (IDbCommand command = connection.CreateCommand())
|
||||
{
|
||||
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_IntentoInicioSesionAdd", cveUser ?? 0);
|
||||
StoredProcedureCommand.AddInput(command, "@CveUsuario", cveUser.HasValue ? (object)cveUser.Value : null, DbType.Int32);
|
||||
StoredProcedureCommand.AddInput(command, "@NombreUsuario", username, DbType.String);
|
||||
StoredProcedureCommand.AddInput(command, "@Exitoso", wasSuccessful, DbType.Boolean);
|
||||
StoredProcedureCommand.AddInput(command, "@MotivoFallo", failureReason, DbType.String);
|
||||
StoredProcedureCommand.AddInput(command, "@DireccionIP", ipAddress, DbType.String);
|
||||
StoredProcedureCommand.AddInput(command, "@AgenteUsuario", userAgent, DbType.String);
|
||||
StoredProcedureCommand.AddInput(command, "@NombreDominio", domainName, DbType.String);
|
||||
|
||||
command.ExecuteNonQuery();
|
||||
StoredProcedureCommand.EnsureSuccess(command);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using SIG.Web.AppCode.Contracts.Database;
|
||||
using SIG.Web.AppCode.Contracts.Security;
|
||||
using SIG.Web.AppCode.Models.Common;
|
||||
using SIG.Web.AppCode.Models.Security;
|
||||
using SIG.Web.AppCode.Services.Database;
|
||||
using SIG.Web.AppCode.Utilities;
|
||||
|
||||
namespace SIG.Web.AppCode.Repositories.Security
|
||||
{
|
||||
public sealed class UserAdministrationRepository : IUserAdministrationRepository
|
||||
{
|
||||
private readonly IDbExecutor _db;
|
||||
public UserAdministrationRepository(IDbExecutor db) { _db = db; }
|
||||
|
||||
public DbResult<List<UserAdministrationModel>> ListUsers(string text, int executingUser, string domainName)
|
||||
{ return QueryList<UserAdministrationModel>("dbo.PA_Sis_UsuarioList", executingUser, domainName, c => StoredProcedureCommand.AddInput(c, "@Texto", text, DbType.String)); }
|
||||
|
||||
public DbResult<UserAdministrationModel> GetUser(int userId, int executingUser, string domainName)
|
||||
{ return QueryOne<UserAdministrationModel>("dbo.PA_Sis_UsuarioGet", executingUser, domainName, c => StoredProcedureCommand.AddInput(c, "@CveUsuario", userId, DbType.Int32)); }
|
||||
|
||||
public DbResult<List<RoleOptionModel>> ListRoles(int executingUser, string domainName)
|
||||
{ return QueryList<RoleOptionModel>("dbo.PA_Sis_RolList", executingUser, domainName, null); }
|
||||
|
||||
public DbResult<List<StateOptionModel>> ListStates(string domainName)
|
||||
{ return QueryList<StateOptionModel>("dbo.PA_Gral_EstadoList", 0, domainName, null); }
|
||||
|
||||
public DbResult<int> SaveUser(UserAdministrationModel user, string passwordHash, int executingUser, string domainName)
|
||||
{
|
||||
return _db.Execute(domainName, connection =>
|
||||
{
|
||||
using (IDbCommand command = connection.CreateCommand())
|
||||
{
|
||||
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_UsuarioSave", executingUser);
|
||||
StoredProcedureCommand.AddInput(command, "@CveUsuario", user.CveUsuario <= 0 ? (object)null : user.CveUsuario, DbType.Int32);
|
||||
StoredProcedureCommand.AddInput(command, "@NombreUsuario", user.NombreUsuario, DbType.String);
|
||||
StoredProcedureCommand.AddInput(command, "@CorreoElectronico", user.CorreoElectronico, DbType.String);
|
||||
StoredProcedureCommand.AddInput(command, "@CveRol", user.CveRol, DbType.Int32);
|
||||
StoredProcedureCommand.AddInput(command, "@HashContrasena", passwordHash, DbType.String);
|
||||
StoredProcedureCommand.AddInput(command, "@Activo", user.Activo, DbType.Boolean);
|
||||
StoredProcedureCommand.AddInput(command, "@DebeCambiarContrasena", user.DebeCambiarContrasena, DbType.Boolean);
|
||||
int id;
|
||||
using (IDataReader reader = command.ExecuteReader()) { id = reader.Read() ? reader.GetInt32(0) : 0; }
|
||||
StoredProcedureCommand.EnsureSuccess(command);
|
||||
return id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public DbResult<bool> DeleteUser(int userId, int executingUser, string domainName)
|
||||
{ return Execute("dbo.PA_Sis_UsuarioDelete", executingUser, domainName, c => StoredProcedureCommand.AddInput(c, "@CveUsuario", userId, DbType.Int32)); }
|
||||
|
||||
public DbResult<List<UserClientModel>> ListUserClients(int userId, int executingUser, string domainName)
|
||||
{ return QueryList<UserClientModel>("dbo.PA_Sis_UsuarioClienteList", executingUser, domainName, c => StoredProcedureCommand.AddInput(c, "@CveUsuario", userId, DbType.Int32)); }
|
||||
|
||||
public DbResult<bool> SaveUserClient(int userId, int clientId, int order, bool primary, int executingUser, string domainName)
|
||||
{
|
||||
return Execute("dbo.PA_Sis_UsuarioClienteSave", executingUser, domainName, c =>
|
||||
{
|
||||
StoredProcedureCommand.AddInput(c, "@CveUsuario", userId, DbType.Int32);
|
||||
StoredProcedureCommand.AddInput(c, "@CveCliente", clientId, DbType.Int32);
|
||||
StoredProcedureCommand.AddInput(c, "@Orden", order, DbType.Int32);
|
||||
StoredProcedureCommand.AddInput(c, "@EsPrincipal", primary, DbType.Boolean);
|
||||
});
|
||||
}
|
||||
|
||||
public DbResult<bool> ValidateRegistration(string username, string email, string rfc, string domainName)
|
||||
{
|
||||
return Execute("dbo.PA_Sis_RegistroClienteValidate", 0, domainName, c =>
|
||||
{
|
||||
StoredProcedureCommand.AddInput(c, "@NombreUsuario", username, DbType.String);
|
||||
StoredProcedureCommand.AddInput(c, "@CorreoElectronico", email, DbType.String);
|
||||
StoredProcedureCommand.AddInput(c, "@RFC", rfc, DbType.String);
|
||||
});
|
||||
}
|
||||
|
||||
public DbResult<RegistrationCreatedModel> CreateRegistration(PublicRegistrationDraft draft, string passwordHash, string tokenHash, string domainName)
|
||||
{
|
||||
return QueryOne<RegistrationCreatedModel>("dbo.PA_Sis_RegistroClienteAdd", 0, domainName, c =>
|
||||
{
|
||||
StoredProcedureCommand.AddInput(c, "@NombreUsuario", draft.NombreUsuario, DbType.String);
|
||||
StoredProcedureCommand.AddInput(c, "@HashContrasena", passwordHash, DbType.String);
|
||||
StoredProcedureCommand.AddInput(c, "@CorreoElectronico", draft.CorreoElectronico, DbType.String);
|
||||
StoredProcedureCommand.AddInput(c, "@RFC", draft.RFC, DbType.String);
|
||||
StoredProcedureCommand.AddInput(c, "@RazonSocial", draft.RazonSocial, DbType.String);
|
||||
StoredProcedureCommand.AddInput(c, "@NombreComercial", draft.NombreComercial, DbType.String);
|
||||
StoredProcedureCommand.AddInput(c, "@Direccion", draft.Direccion, DbType.String);
|
||||
StoredProcedureCommand.AddInput(c, "@NoExt", draft.NoExt, DbType.String);
|
||||
StoredProcedureCommand.AddInput(c, "@NoInt", draft.NoInt, DbType.String);
|
||||
StoredProcedureCommand.AddInput(c, "@Colonia", draft.Colonia, DbType.String);
|
||||
StoredProcedureCommand.AddInput(c, "@Ciudad", draft.Ciudad, DbType.String);
|
||||
StoredProcedureCommand.AddInput(c, "@CveSATEstado", draft.CveSATEstado, DbType.Int32);
|
||||
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, "@HashToken", tokenHash, DbType.StringFixedLength);
|
||||
StoredProcedureCommand.AddInput(c, "@HorasExpiracion", 24, DbType.Int32);
|
||||
});
|
||||
}
|
||||
|
||||
public DbResult<bool> VerifyEmail(string tokenHash, string domainName)
|
||||
{ return Execute("dbo.PA_Sis_CorreoVerify", 0, domainName, c => StoredProcedureCommand.AddInput(c, "@HashToken", tokenHash, DbType.StringFixedLength)); }
|
||||
|
||||
private DbResult<List<T>> QueryList<T>(string procedure, int user, string domain, System.Action<IDbCommand> parameters) where T : class, new()
|
||||
{
|
||||
return _db.Execute(domain, connection =>
|
||||
{
|
||||
using (IDbCommand command = connection.CreateCommand())
|
||||
{
|
||||
StoredProcedureCommand.Configure(command, procedure, user); if (parameters != null) parameters(command);
|
||||
List<T> value; using (IDataReader reader = command.ExecuteReader()) { value = reader.ToList<T>(); }
|
||||
StoredProcedureCommand.EnsureSuccess(command); return value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private DbResult<T> QueryOne<T>(string procedure, int user, string domain, System.Action<IDbCommand> parameters) where T : class, new()
|
||||
{
|
||||
return _db.Execute(domain, connection =>
|
||||
{
|
||||
using (IDbCommand command = connection.CreateCommand())
|
||||
{
|
||||
StoredProcedureCommand.Configure(command, procedure, user); if (parameters != null) parameters(command);
|
||||
T value; using (IDataReader reader = command.ExecuteReader()) { value = reader.ToObject<T>(); }
|
||||
StoredProcedureCommand.EnsureSuccess(command); return value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private DbResult<bool> Execute(string procedure, int user, string domain, System.Action<IDbCommand> parameters)
|
||||
{
|
||||
return _db.Execute(domain, connection =>
|
||||
{
|
||||
using (IDbCommand command = connection.CreateCommand())
|
||||
{
|
||||
StoredProcedureCommand.Configure(command, procedure, user); if (parameters != null) parameters(command);
|
||||
command.ExecuteNonQuery(); StoredProcedureCommand.EnsureSuccess(command); return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
68
SIG.Web/AppCode/Repositories/Security/UserRepository.cs
Normal file
68
SIG.Web/AppCode/Repositories/Security/UserRepository.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using SIG.Web.AppCode.Contracts.Database;
|
||||
using SIG.Web.AppCode.Contracts.Security;
|
||||
using SIG.Web.AppCode.Models.Common;
|
||||
using SIG.Web.AppCode.Models.Security;
|
||||
using SIG.Web.AppCode.Services.Database;
|
||||
using SIG.Web.AppCode.Utilities;
|
||||
|
||||
namespace SIG.Web.AppCode.Repositories.Security
|
||||
{
|
||||
public sealed class UserRepository : IUserRepository
|
||||
{
|
||||
private readonly IDbExecutor _dbExecutor;
|
||||
|
||||
public UserRepository(IDbExecutor dbExecutor)
|
||||
{
|
||||
_dbExecutor = dbExecutor;
|
||||
}
|
||||
|
||||
public DbResult<UserModel> ObtenerPorNombreUsuario(string username, string domainName)
|
||||
{
|
||||
return _dbExecutor.Execute(domainName, connection =>
|
||||
{
|
||||
using (IDbCommand command = connection.CreateCommand())
|
||||
{
|
||||
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_UsuarioGetByName", 0);
|
||||
StoredProcedureCommand.AddInput(command, "@NombreUsuario", username, DbType.String);
|
||||
|
||||
UserModel user;
|
||||
|
||||
using (IDataReader reader = command.ExecuteReader())
|
||||
{
|
||||
user = reader.ToObject<UserModel>();
|
||||
}
|
||||
|
||||
StoredProcedureCommand.EnsureSuccess(command);
|
||||
return user;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public DbResult<bool> RegisterSuccessfulLogin(int cveUser, string domainName)
|
||||
{
|
||||
return ExecuteUserUpdate("dbo.PA_Sis_UsuarioLoginSuccessUpdate", cveUser, domainName);
|
||||
}
|
||||
|
||||
public DbResult<bool> RegisterFailedLogin(int cveUser, string domainName)
|
||||
{
|
||||
return ExecuteUserUpdate("dbo.PA_Sis_UsuarioLoginFailedUpdate", cveUser, domainName);
|
||||
}
|
||||
|
||||
private DbResult<bool> ExecuteUserUpdate(string procedureName, int cveUser, string domainName)
|
||||
{
|
||||
return _dbExecutor.Execute(domainName, connection =>
|
||||
{
|
||||
using (IDbCommand command = connection.CreateCommand())
|
||||
{
|
||||
StoredProcedureCommand.Configure(command, procedureName, cveUser);
|
||||
StoredProcedureCommand.AddInput(command, "@CveUsuario", cveUser, DbType.Int32);
|
||||
command.ExecuteNonQuery();
|
||||
StoredProcedureCommand.EnsureSuccess(command);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Data;
|
||||
using SIG.Web.AppCode.Contracts.Database;
|
||||
using SIG.Web.AppCode.Contracts.Security;
|
||||
using SIG.Web.AppCode.Models.Common;
|
||||
using SIG.Web.AppCode.Services.Database;
|
||||
|
||||
namespace SIG.Web.AppCode.Repositories.Security
|
||||
{
|
||||
public sealed class UserSessionRepository : IUserSessionRepository
|
||||
{
|
||||
private readonly IDbExecutor _dbExecutor;
|
||||
|
||||
public UserSessionRepository(IDbExecutor dbExecutor)
|
||||
{
|
||||
_dbExecutor = dbExecutor;
|
||||
}
|
||||
|
||||
public DbResult<bool> StartSession(int cveUser, string sessionToken, string ipAddress, string userAgent, string domainName)
|
||||
{
|
||||
return _dbExecutor.Execute(domainName, connection =>
|
||||
{
|
||||
using (IDbCommand command = connection.CreateCommand())
|
||||
{
|
||||
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_UsuarioSesionAdd", cveUser);
|
||||
StoredProcedureCommand.AddInput(command, "@CveUsuario", cveUser, DbType.Int32);
|
||||
StoredProcedureCommand.AddInput(command, "@TokenSesion", sessionToken, DbType.String);
|
||||
StoredProcedureCommand.AddInput(command, "@DireccionIP", ipAddress, DbType.String);
|
||||
StoredProcedureCommand.AddInput(command, "@AgenteUsuario", userAgent, DbType.String);
|
||||
StoredProcedureCommand.AddInput(command, "@NombreDominio", domainName, DbType.String);
|
||||
|
||||
command.ExecuteNonQuery();
|
||||
StoredProcedureCommand.EnsureSuccess(command);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public DbResult<bool> EndSession(string sessionToken, string domainName)
|
||||
{
|
||||
return _dbExecutor.Execute(domainName, connection =>
|
||||
{
|
||||
using (IDbCommand command = connection.CreateCommand())
|
||||
{
|
||||
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_UsuarioSesionEnd", 0);
|
||||
StoredProcedureCommand.AddInput(command, "@TokenSesion", sessionToken, DbType.String);
|
||||
|
||||
command.ExecuteNonQuery();
|
||||
StoredProcedureCommand.EnsureSuccess(command);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
109
SIG.Web/AppCode/Security/ClsPaginaBase.cs
Normal file
109
SIG.Web/AppCode/Security/ClsPaginaBase.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using SIG.Web.AppCode.Configuration;
|
||||
using SIG.Web.AppCode.Services.Logging;
|
||||
using SIG.Web.AppCode.Session;
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.Web.UI;
|
||||
|
||||
namespace SIG.Web.AppCode.Security
|
||||
{
|
||||
public abstract class ClsPaginaBase : Page
|
||||
{
|
||||
protected UserSessionModel CurrentUser { get; private set; }
|
||||
|
||||
protected override void OnInit(EventArgs e)
|
||||
{
|
||||
base.OnInit(e);
|
||||
|
||||
string currentPath = Request.AppRelativeCurrentExecutionFilePath;
|
||||
|
||||
if (IsPublicPage(currentPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentUser = AppServices.UserContext.GetCurrentUser();
|
||||
|
||||
if (CurrentUser == null)
|
||||
{
|
||||
Response.Redirect(BuildLoginUrl(currentPath), false);
|
||||
Context.ApplicationInstance.CompleteRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsHomePage(currentPath) &&
|
||||
!AppServices.PermissionService.CanAccessPage(CurrentUser.CveUsuario, currentPath))
|
||||
{
|
||||
Response.Redirect("~/AccessDenied.aspx", false);
|
||||
Context.ApplicationInstance.CompleteRequest();
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildLoginUrl(string currentPath)
|
||||
{
|
||||
string loginUrl = ResolveUrl("~/Login.aspx");
|
||||
|
||||
if (IsHomePage(currentPath))
|
||||
{
|
||||
return loginUrl;
|
||||
}
|
||||
|
||||
string retornoURL =
|
||||
Request.Url == null
|
||||
? null
|
||||
: Request.Url.PathAndQuery;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(retornoURL) ||
|
||||
retornoURL.Equals("/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return loginUrl;
|
||||
}
|
||||
|
||||
return loginUrl +
|
||||
"?retornoURL=" +
|
||||
HttpUtility.UrlEncode(retornoURL);
|
||||
}
|
||||
|
||||
private static bool IsHomePage(string appRelativePath)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(appRelativePath) ||
|
||||
appRelativePath.Equals("~/Inicio.aspx", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsPublicPage(string appRelativePath)
|
||||
{
|
||||
return appRelativePath != null &&
|
||||
(appRelativePath.Equals("~/Login.aspx", StringComparison.OrdinalIgnoreCase) ||
|
||||
appRelativePath.Equals("~/Logout.aspx", StringComparison.OrdinalIgnoreCase) ||
|
||||
appRelativePath.Equals("~/AccessDenied.aspx", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
protected override void OnError(EventArgs e)
|
||||
{
|
||||
Exception exception = Server.GetLastError();
|
||||
|
||||
if (exception == null)
|
||||
{
|
||||
base.OnError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
ExceptionLogger.Log(
|
||||
exception,
|
||||
GetType().FullName
|
||||
);
|
||||
|
||||
string message =
|
||||
ErrorHandlerConfig.GetFriendlyMessage(exception);
|
||||
|
||||
Server.ClearError();
|
||||
|
||||
Site siteMaster = Master as Site;
|
||||
|
||||
if (siteMaster != null)
|
||||
{
|
||||
siteMaster.ShowGlobalError(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
SIG.Web/AppCode/Security/DummyPermissionService.cs
Normal file
12
SIG.Web/AppCode/Security/DummyPermissionService.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using SIG.Web.AppCode.Session;
|
||||
|
||||
namespace SIG.Web.AppCode.Security
|
||||
{
|
||||
public sealed class DummyPermissionService : IPermissionService
|
||||
{
|
||||
public bool CanAccess(UserSessionModel user, string appRelativePath)
|
||||
{
|
||||
return user != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
SIG.Web/AppCode/Security/IPermissionService.cs
Normal file
9
SIG.Web/AppCode/Security/IPermissionService.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using SIG.Web.AppCode.Session;
|
||||
|
||||
namespace SIG.Web.AppCode.Security
|
||||
{
|
||||
public interface IPermissionService
|
||||
{
|
||||
bool CanAccess(UserSessionModel user, string appRelativePath);
|
||||
}
|
||||
}
|
||||
37
SIG.Web/AppCode/Services/Data/DbConnectionFactory.cs
Normal file
37
SIG.Web/AppCode/Services/Data/DbConnectionFactory.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Npgsql;
|
||||
using SIG.Web.AppCode.Contracts.Data;
|
||||
|
||||
namespace SIG.Web.AppCode.Services.Data
|
||||
{
|
||||
public sealed class DbConnectionFactory : IDbConnectionFactory
|
||||
{
|
||||
private readonly IDomainConnectionResolver _resolver;
|
||||
|
||||
public DbConnectionFactory(IDomainConnectionResolver resolver)
|
||||
{
|
||||
_resolver = resolver;
|
||||
}
|
||||
|
||||
public IDbConnection Create(string domain)
|
||||
{
|
||||
var settings = _resolver.Resolve(domain);
|
||||
var connection = ConfigurationManager.ConnectionStrings[settings.ConnectionStringName];
|
||||
if (connection == null)
|
||||
throw new InvalidOperationException("Connection string not found: " + settings.ConnectionStringName);
|
||||
|
||||
switch (settings.Provider)
|
||||
{
|
||||
case DbProviderKind.SqlServer:
|
||||
return new SqlConnection(connection.ConnectionString);
|
||||
case DbProviderKind.PostgreSql:
|
||||
return new NpgsqlConnection(connection.ConnectionString);
|
||||
default:
|
||||
throw new NotSupportedException("Unsupported database provider: " + settings.Provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
SIG.Web/AppCode/Services/Data/DomainConnectionResolver.cs
Normal file
28
SIG.Web/AppCode/Services/Data/DomainConnectionResolver.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using SIG.Web.AppCode.Contracts.Data;
|
||||
|
||||
namespace SIG.Web.AppCode.Services.Data
|
||||
{
|
||||
public sealed class DomainConnectionResolver : IDomainConnectionResolver
|
||||
{
|
||||
public DomainConnectionSettings Resolve(string domain)
|
||||
{
|
||||
// Default first. Later this can read a table or config mapping by host/domain.
|
||||
var providerText = ConfigurationManager.AppSettings["DefaultDbProvider"] ?? "SqlServer";
|
||||
var connectionName = ConfigurationManager.AppSettings["DefaultConnectionStringName"] ?? "SIG_DefaultSql";
|
||||
|
||||
DbProviderKind provider;
|
||||
if (!Enum.TryParse(providerText, true, out provider))
|
||||
provider = DbProviderKind.SqlServer;
|
||||
|
||||
return new DomainConnectionSettings
|
||||
{
|
||||
Domain = domain,
|
||||
Provider = provider,
|
||||
ConnectionStringName = connectionName,
|
||||
IsDefault = true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
19
SIG.Web/AppCode/Services/Database/DbConnectionFactory.cs
Normal file
19
SIG.Web/AppCode/Services/Database/DbConnectionFactory.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
162
SIG.Web/AppCode/Services/Database/DbExecutor.cs
Normal file
162
SIG.Web/AppCode/Services/Database/DbExecutor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
133
SIG.Web/AppCode/Services/Database/StoredProcedureCommand.cs
Normal file
133
SIG.Web/AppCode/Services/Database/StoredProcedureCommand.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
276
SIG.Web/AppCode/Services/Database/StoredProcedureDiagnostics.cs
Normal file
276
SIG.Web/AppCode/Services/Database/StoredProcedureDiagnostics.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
720
SIG.Web/AppCode/Services/Error/SIGErrorManager.cs
Normal file
720
SIG.Web/AppCode/Services/Error/SIGErrorManager.cs
Normal file
@@ -0,0 +1,720 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Web.Hosting;
|
||||
using SIG.Web.AppCode.Configuration;
|
||||
using SIG.Web.AppCode.Session;
|
||||
|
||||
namespace SIG.Web.AppCode.Services.Errors
|
||||
{
|
||||
public sealed class SIGErrorRegistro
|
||||
{
|
||||
public string Codigo { get; set; }
|
||||
public DateTime Fecha { get; set; }
|
||||
public string Origen { get; set; }
|
||||
public string CveUsuario { get; set; }
|
||||
public string NombreUsuario { get; set; }
|
||||
public string MetodoHttp { get; set; }
|
||||
public string Ruta { get; set; }
|
||||
public string Ip { get; set; }
|
||||
public string AgenteUsuario { get; set; }
|
||||
public bool EsPostbackAsincrono { get; set; }
|
||||
public string DetalleTecnico { get; set; }
|
||||
}
|
||||
|
||||
public interface ISIGErrorStore
|
||||
{
|
||||
void Guardar(SIGErrorRegistro registro);
|
||||
|
||||
void MarcarReportado(
|
||||
string codigo,
|
||||
DateTime fecha,
|
||||
string cveUsuario,
|
||||
string nombreUsuario,
|
||||
string ruta);
|
||||
}
|
||||
|
||||
public sealed class SIGTextFileErrorStore : ISIGErrorStore
|
||||
{
|
||||
private static readonly object BloqueoArchivo = new object();
|
||||
|
||||
public void Guardar(SIGErrorRegistro registro)
|
||||
{
|
||||
if (registro == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder texto = new StringBuilder();
|
||||
|
||||
texto.AppendLine();
|
||||
texto.AppendLine("============================================================");
|
||||
texto.AppendLine("ERROR");
|
||||
texto.AppendLine("============================================================");
|
||||
texto.AppendLine("Codigo: " + Safe(registro.Codigo));
|
||||
texto.AppendLine("Fecha: " + registro.Fecha.ToString("yyyy-MM-dd HH:mm:ss.fff"));
|
||||
texto.AppendLine("Origen: " + Safe(registro.Origen));
|
||||
texto.AppendLine("CveUsuario: " + Safe(registro.CveUsuario));
|
||||
texto.AppendLine("Nombre: " + Safe(registro.NombreUsuario));
|
||||
texto.AppendLine("Metodo: " + Safe(registro.MetodoHttp));
|
||||
texto.AppendLine("Ruta: " + Safe(registro.Ruta));
|
||||
texto.AppendLine("IP: " + Safe(registro.Ip));
|
||||
texto.AppendLine("AgenteUsuario: " + Safe(registro.AgenteUsuario));
|
||||
texto.AppendLine("AsyncPostBack: " + registro.EsPostbackAsincrono);
|
||||
texto.AppendLine();
|
||||
texto.AppendLine("Detalle tecnico:");
|
||||
texto.AppendLine(Safe(registro.DetalleTecnico));
|
||||
texto.AppendLine("============================================================");
|
||||
|
||||
Escribir(texto.ToString());
|
||||
}
|
||||
|
||||
public void MarcarReportado(
|
||||
string codigo,
|
||||
DateTime fecha,
|
||||
string cveUsuario,
|
||||
string nombreUsuario,
|
||||
string ruta)
|
||||
{
|
||||
StringBuilder texto = new StringBuilder();
|
||||
|
||||
texto.AppendLine();
|
||||
texto.AppendLine("------------------------------------------------------------");
|
||||
texto.AppendLine("REPORTE DEL USUARIO");
|
||||
texto.AppendLine("Codigo: " + Safe(codigo));
|
||||
texto.AppendLine("Fecha: " + fecha.ToString("yyyy-MM-dd HH:mm:ss.fff"));
|
||||
texto.AppendLine("CveUsuario: " + Safe(cveUsuario));
|
||||
texto.AppendLine("Nombre: " + Safe(nombreUsuario));
|
||||
texto.AppendLine("Ruta: " + Safe(ruta));
|
||||
texto.AppendLine("------------------------------------------------------------");
|
||||
|
||||
Escribir(texto.ToString());
|
||||
}
|
||||
|
||||
private static void Escribir(string texto)
|
||||
{
|
||||
string ruta = HostingEnvironment.MapPath("~/log/log.txt");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ruta))
|
||||
{
|
||||
ruta = Path.Combine(
|
||||
AppDomain.CurrentDomain.BaseDirectory,
|
||||
"log",
|
||||
"log.txt");
|
||||
}
|
||||
|
||||
string directorio = Path.GetDirectoryName(ruta);
|
||||
|
||||
lock (BloqueoArchivo)
|
||||
{
|
||||
if (!Directory.Exists(directorio))
|
||||
{
|
||||
Directory.CreateDirectory(directorio);
|
||||
}
|
||||
|
||||
using (FileStream stream = new FileStream(
|
||||
ruta,
|
||||
FileMode.Append,
|
||||
FileAccess.Write,
|
||||
FileShare.Read))
|
||||
using (StreamWriter writer = new StreamWriter(
|
||||
stream,
|
||||
new UTF8Encoding(false)))
|
||||
{
|
||||
writer.Write(texto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string Safe(string value)
|
||||
{
|
||||
return string.IsNullOrEmpty(value) ? string.Empty : value;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SIGErrorManager
|
||||
{
|
||||
private const string ErrorCodeDataKey = "SIG.ErrorCode";
|
||||
|
||||
private static ISIGErrorStore _store =
|
||||
new SIGTextFileErrorStore();
|
||||
|
||||
public static ISIGErrorStore Store
|
||||
{
|
||||
get { return _store; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
|
||||
_store = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static string Registrar(
|
||||
Exception exception,
|
||||
HttpContext context,
|
||||
string origen,
|
||||
string cveUsuario,
|
||||
string nombreUsuario)
|
||||
{
|
||||
if (exception == null)
|
||||
{
|
||||
exception = new Exception(
|
||||
"No se recibio la excepcion original.");
|
||||
}
|
||||
|
||||
string codigoExistente =
|
||||
ObtenerCodigoExistente(exception);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(codigoExistente))
|
||||
{
|
||||
return codigoExistente;
|
||||
}
|
||||
|
||||
string codigo =
|
||||
"SIG-" +
|
||||
DateTime.Now.ToString("yyyyMMdd") +
|
||||
"-" +
|
||||
Guid.NewGuid()
|
||||
.ToString("N")
|
||||
.Substring(0, 6)
|
||||
.ToUpperInvariant();
|
||||
|
||||
AsignarCodigo(exception, codigo);
|
||||
|
||||
string metodo = string.Empty;
|
||||
string ruta = string.Empty;
|
||||
string ip = string.Empty;
|
||||
string userAgent = string.Empty;
|
||||
bool esAsync = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (context != null && context.Request != null)
|
||||
{
|
||||
metodo = context.Request.HttpMethod;
|
||||
ruta = context.Request.RawUrl;
|
||||
ip = context.Request.UserHostAddress;
|
||||
userAgent = context.Request.UserAgent;
|
||||
esAsync = EsPeticionAsincrona(context);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// El contexto no debe provocar otro error.
|
||||
}
|
||||
|
||||
SIGErrorRegistro registro = new SIGErrorRegistro
|
||||
{
|
||||
Codigo = codigo,
|
||||
Fecha = DateTime.Now,
|
||||
Origen = origen,
|
||||
CveUsuario = cveUsuario,
|
||||
NombreUsuario = nombreUsuario,
|
||||
MetodoHttp = metodo,
|
||||
Ruta = ruta,
|
||||
Ip = ip,
|
||||
AgenteUsuario = userAgent,
|
||||
EsPostbackAsincrono = esAsync,
|
||||
DetalleTecnico = ObtenerDetalleTecnico(exception)
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
_store.Guardar(registro);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Nunca lanzar otro error desde el manejador global.
|
||||
}
|
||||
|
||||
return codigo;
|
||||
}
|
||||
|
||||
public static void MarcarReportado(
|
||||
string codigo,
|
||||
HttpContext context,
|
||||
string cveUsuario,
|
||||
string nombreUsuario)
|
||||
{
|
||||
string ruta = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
if (context != null && context.Request != null)
|
||||
{
|
||||
ruta = context.Request.Form["ruta"];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ruta))
|
||||
{
|
||||
ruta = context.Request.RawUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ruta = string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_store.MarcarReportado(
|
||||
codigo,
|
||||
DateTime.Now,
|
||||
cveUsuario,
|
||||
nombreUsuario,
|
||||
ruta);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Nunca lanzar desde el registro de reporte.
|
||||
}
|
||||
}
|
||||
|
||||
public static void ObtenerUsuarioActual(
|
||||
out string cveUsuario,
|
||||
out string nombreUsuario,
|
||||
out int cveRole)
|
||||
{
|
||||
cveUsuario = string.Empty;
|
||||
nombreUsuario = string.Empty;
|
||||
cveRole = 0;
|
||||
|
||||
try
|
||||
{
|
||||
if (AppServices.UserContext != null)
|
||||
{
|
||||
UserSessionModel usuario =
|
||||
AppServices.UserContext.GetCurrentUser();
|
||||
|
||||
if (usuario != null)
|
||||
{
|
||||
cveUsuario =
|
||||
Convert.ToString(usuario.CveUsuario);
|
||||
|
||||
nombreUsuario =
|
||||
Convert.ToString(usuario.Nombre);
|
||||
|
||||
cveRole = usuario.CveRol;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Si Session no esta disponible, continuar con Items.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
HttpContext context = HttpContext.Current;
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
object itemCveUsuario =
|
||||
context.Items["SIG.Error.CveUsuario"];
|
||||
|
||||
if (itemCveUsuario == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cveUsuario =
|
||||
Convert.ToString(itemCveUsuario);
|
||||
|
||||
nombreUsuario =
|
||||
Convert.ToString(
|
||||
context.Items["SIG.Error.NombreUsuario"]);
|
||||
|
||||
int.TryParse(
|
||||
Convert.ToString(
|
||||
context.Items["SIG.Error.CveRol"]),
|
||||
out cveRole);
|
||||
|
||||
if (cveRole == 0)
|
||||
{
|
||||
int.TryParse(
|
||||
Convert.ToString(
|
||||
context.Items["SIG.Error.CveRol"]),
|
||||
out cveRole);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
cveUsuario = string.Empty;
|
||||
nombreUsuario = string.Empty;
|
||||
cveRole = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool EsPeticionAsincrona(
|
||||
HttpContext context)
|
||||
{
|
||||
if (context == null || context.Request == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string encabezado =
|
||||
context.Request.Headers["X-MicrosoftAjax"];
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(encabezado) &&
|
||||
encabezado.IndexOf(
|
||||
"Delta=true",
|
||||
StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return string.Equals(
|
||||
context.Request.Form["__ASYNCPOST"],
|
||||
"true",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string CrearHtmlError(
|
||||
string codigo,
|
||||
Exception exception,
|
||||
bool mostrarDetalle,
|
||||
string cveUsuarioActual,
|
||||
string nombreUsuarioActual,
|
||||
int cveRoleActual)
|
||||
{
|
||||
string codigoSeguro =
|
||||
HttpUtility.HtmlEncode(codigo);
|
||||
|
||||
string codigoJavaScript =
|
||||
HttpUtility.JavaScriptStringEncode(codigo);
|
||||
|
||||
string cveUsuarioJavaScript =
|
||||
HttpUtility.JavaScriptStringEncode(
|
||||
cveUsuarioActual ?? string.Empty);
|
||||
|
||||
string nombreUsuarioSeguro =
|
||||
HttpUtility.HtmlEncode(
|
||||
nombreUsuarioActual ?? string.Empty);
|
||||
|
||||
string urlInicio =
|
||||
VirtualPathUtility.ToAbsolute("~/Inicio.aspx");
|
||||
|
||||
string urlReporte =
|
||||
VirtualPathUtility.ToAbsolute(
|
||||
"~/ashx/ReportarError.ashx");
|
||||
|
||||
string detalleHtml = string.Empty;
|
||||
|
||||
if (mostrarDetalle && exception != null)
|
||||
{
|
||||
detalleHtml =
|
||||
"<details class=\"sig-error-details\">" +
|
||||
"<summary>Ver detalle tecnico</summary>" +
|
||||
"<pre>" +
|
||||
HttpUtility.HtmlEncode(
|
||||
ObtenerDetalleTecnico(exception)) +
|
||||
"</pre>" +
|
||||
"</details>";
|
||||
}
|
||||
|
||||
StringBuilder html = new StringBuilder();
|
||||
|
||||
html.AppendLine("<!DOCTYPE html>");
|
||||
html.AppendLine("<html lang=\"es\">");
|
||||
html.AppendLine("<head>");
|
||||
html.AppendLine(" <meta charset=\"utf-8\">");
|
||||
html.AppendLine(" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
|
||||
html.AppendLine(" <title>SIG - Error inesperado</title>");
|
||||
html.AppendLine(" <link rel=\"shortcut icon\" href=\"/Imagenes/favicon.ico\">");
|
||||
html.AppendLine(" <style>");
|
||||
html.AppendLine(" html,body{margin:0;min-height:100%;font-family:'Open Sans',Arial,sans-serif;background:#eef1f5;color:#333}");
|
||||
html.AppendLine(" *{box-sizing:border-box}");
|
||||
html.AppendLine(" .sig-header{position:fixed;top:0;left:0;right:0;height:46px;z-index:10;display:flex;align-items:center;justify-content:space-between;padding:0 18px;background:#2b3643;color:#fff;box-shadow:0 1px 4px rgba(0,0,0,.18)}");
|
||||
html.AppendLine(" .sig-brand{display:flex;align-items:center;gap:10px;font-size:15px;font-weight:600}");
|
||||
html.AppendLine(" .sig-brand-box{display:flex;align-items:center;justify-content:center;width:34px;height:30px;border-radius:3px;background:#3598dc;font-size:13px;font-weight:700;letter-spacing:.5px}");
|
||||
html.AppendLine(" .sig-header-status{display:flex;flex-direction:column;align-items:flex-end;color:#c6cfda;font-size:12px;line-height:16px}");
|
||||
html.AppendLine(" .sig-header-user{color:#fff;font-weight:600}");
|
||||
html.AppendLine(" .sig-sidebar{position:fixed;top:46px;bottom:0;left:0;width:235px;padding-top:18px;background:#364150}");
|
||||
html.AppendLine(" .sig-sidebar-title{padding:7px 20px 14px;color:#708096;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.6px}");
|
||||
html.AppendLine(" .sig-restored-menu{margin:0;padding:0;list-style:none}");
|
||||
html.AppendLine(" .sig-restored-menu li{margin:0;padding:0;list-style:none}");
|
||||
html.AppendLine(" .sig-restored-menu a,.sig-restored-menu button{display:flex;align-items:center;gap:12px;width:100%;padding:12px 20px;border:0;background:transparent;color:#b4bcc8;font-family:inherit;font-size:13px;text-decoration:none;text-align:left;cursor:pointer}");
|
||||
html.AppendLine(" .sig-restored-menu a:hover,.sig-restored-menu button:hover{background:#2c3542;color:#fff}");
|
||||
html.AppendLine(" .sig-restored-menu .sub-menu{display:none;margin:0;padding:0;background:#2c3542}");
|
||||
html.AppendLine(" .sig-restored-menu li.open>.sub-menu{display:block}");
|
||||
html.AppendLine(" .sig-restored-menu .sub-menu a{padding-left:42px;font-size:12px}");
|
||||
html.AppendLine(" .sig-main{min-height:100vh;margin-left:235px;padding:70px 28px 35px}");
|
||||
html.AppendLine(" .sig-page-title{margin:0 0 14px;color:#666;font-size:22px;font-weight:300}");
|
||||
html.AppendLine(" .sig-breadcrumb{margin-bottom:22px;padding:10px 14px;background:#fff;color:#888;font-size:12px}");
|
||||
html.AppendLine(" .sig-error-card{max-width:820px;background:#fff;border:1px solid #e5e5e5;border-left:4px solid #e43a45;box-shadow:0 2px 5px rgba(0,0,0,.07)}");
|
||||
html.AppendLine(" .sig-error-card-header{display:flex;align-items:center;gap:13px;padding:18px 22px;border-bottom:1px solid #eef1f5}");
|
||||
html.AppendLine(" .sig-error-icon{display:flex;align-items:center;justify-content:center;width:38px;height:38px;flex:0 0 38px;border-radius:50%;background:#fbeaec;color:#e43a45;font-size:21px;font-weight:700}");
|
||||
html.AppendLine(" .sig-error-title{margin:0 0 3px;color:#333;font-size:17px;font-weight:600}");
|
||||
html.AppendLine(" .sig-error-subtitle{color:#777;font-size:13px}");
|
||||
html.AppendLine(" .sig-error-body{padding:22px}");
|
||||
html.AppendLine(" .sig-error-body p{margin:0 0 14px;line-height:1.6}");
|
||||
html.AppendLine(" .sig-error-code-label{margin-bottom:6px;color:#777;font-size:12px;font-weight:600;text-transform:uppercase}");
|
||||
html.AppendLine(" .sig-error-code{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 13px;background:#f4f6f8;border:1px solid #e7e9ec;border-radius:3px;font-family:Consolas,monospace;font-size:14px;font-weight:700}");
|
||||
html.AppendLine(" .sig-copy-button{border:0;background:transparent;color:#3598dc;font-family:inherit;font-size:12px;cursor:pointer}");
|
||||
html.AppendLine(" .sig-error-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px;margin-top:20px}");
|
||||
html.AppendLine(" .sig-btn{min-height:34px;padding:7px 15px;border:1px solid transparent;border-radius:2px;font-family:inherit;font-size:13px;cursor:pointer;text-decoration:none}");
|
||||
html.AppendLine(" .sig-btn-default{border-color:#d8d8d8;background:#fff;color:#555}");
|
||||
html.AppendLine(" .sig-btn-primary{border-color:#258fd7;background:#3598dc;color:#fff}");
|
||||
html.AppendLine(" .sig-btn-danger{border-color:#d52b37;background:#e43a45;color:#fff}");
|
||||
html.AppendLine(" .sig-btn:disabled{cursor:wait;opacity:.6}");
|
||||
html.AppendLine(" .sig-report-result{min-height:18px;margin-top:14px;color:#555;font-size:12px}");
|
||||
html.AppendLine(" .sig-error-details{margin-top:20px;border-top:1px solid #eee;padding-top:15px}");
|
||||
html.AppendLine(" .sig-error-details summary{color:#3598dc;font-size:13px;font-weight:600;cursor:pointer}");
|
||||
html.AppendLine(" .sig-error-details pre{max-height:380px;margin:13px 0 0;padding:14px;overflow:auto;white-space:pre-wrap;word-break:break-word;border-radius:3px;background:#263238;color:#f2f2f2;font-family:Consolas,monospace;font-size:12px;line-height:1.45}");
|
||||
html.AppendLine(" @media(max-width:800px){.sig-sidebar{display:none}.sig-main{margin-left:0;padding:65px 14px 25px}.sig-header-status{display:none}.sig-error-actions{flex-direction:column}.sig-btn{width:100%;text-align:center}}");
|
||||
html.AppendLine(" </style>");
|
||||
html.AppendLine("</head>");
|
||||
html.AppendLine("<body>");
|
||||
|
||||
html.AppendLine("<header class=\"sig-header\">");
|
||||
html.AppendLine(" <div class=\"sig-brand\">");
|
||||
html.AppendLine(" <div class=\"sig-brand-box\">SIG</div>");
|
||||
html.AppendLine(" <span>Sistema de Informacion Gerencial</span>");
|
||||
html.AppendLine(" </div>");
|
||||
html.AppendLine(" <div class=\"sig-header-status\">");
|
||||
if (!string.IsNullOrWhiteSpace(nombreUsuarioSeguro))
|
||||
{
|
||||
html.AppendLine(" <span class=\"sig-header-user\">" + nombreUsuarioSeguro + "</span>");
|
||||
}
|
||||
html.AppendLine(" </div>");
|
||||
html.AppendLine("</header>");
|
||||
|
||||
html.AppendLine("<aside class=\"sig-sidebar\">");
|
||||
html.AppendLine(" <div class=\"sig-sidebar-title\">Navegacion</div>");
|
||||
html.AppendLine(" <ul id=\"sigMenuRestaurado\" class=\"sig-restored-menu\">");
|
||||
html.AppendLine(" <li><a href=\"" + HttpUtility.HtmlAttributeEncode(urlInicio) + "\"><span>⌂</span><span>Inicio</span></a></li>");
|
||||
html.AppendLine(" <li><button type=\"button\" onclick=\"history.back();\"><span>←</span><span>Regresar</span></button></li>");
|
||||
html.AppendLine(" </ul>");
|
||||
html.AppendLine("</aside>");
|
||||
|
||||
html.AppendLine("<main class=\"sig-main\">");
|
||||
html.AppendLine(" <h1 class=\"sig-page-title\">Error inesperado</h1>");
|
||||
html.AppendLine(" <div class=\"sig-breadcrumb\">Inicio / Error</div>");
|
||||
html.AppendLine(" <section class=\"sig-error-card\">");
|
||||
html.AppendLine(" <div class=\"sig-error-card-header\">");
|
||||
html.AppendLine(" <div class=\"sig-error-icon\">!</div>");
|
||||
html.AppendLine(" <div>");
|
||||
html.AppendLine(" <h2 class=\"sig-error-title\">No fue posible completar la operacion</h2>");
|
||||
html.AppendLine(" <div class=\"sig-error-subtitle\">La aplicacion sigue disponible.</div>");
|
||||
html.AppendLine(" </div>");
|
||||
html.AppendLine(" </div>");
|
||||
html.AppendLine(" <div class=\"sig-error-body\">");
|
||||
html.AppendLine(" <p>Ocurrio un error inesperado al cargar esta seccion. Puedes regresar, intentar nuevamente o reportarlo al area de soporte.</p>");
|
||||
html.AppendLine(" <div class=\"sig-error-code-label\">Codigo para soporte</div>");
|
||||
html.AppendLine(" <div class=\"sig-error-code\">");
|
||||
html.AppendLine(" <span id=\"sigCodigoError\">" + codigoSeguro + "</span>");
|
||||
html.AppendLine(" <button type=\"button\" class=\"sig-copy-button\" onclick=\"sigCopiarCodigo();\">Copiar codigo</button>");
|
||||
html.AppendLine(" </div>");
|
||||
html.AppendLine(" <div id=\"sigResultadoReporte\" class=\"sig-report-result\"></div>");
|
||||
html.AppendLine(detalleHtml);
|
||||
html.AppendLine(" <div class=\"sig-error-actions\">");
|
||||
html.AppendLine(" <button type=\"button\" class=\"sig-btn sig-btn-default\" onclick=\"history.back();\">Regresar</button>");
|
||||
html.AppendLine(" <a class=\"sig-btn sig-btn-default\" href=\"" + HttpUtility.HtmlAttributeEncode(urlInicio) + "\">Ir al inicio</a>");
|
||||
html.AppendLine(" <button type=\"button\" class=\"sig-btn sig-btn-primary\" onclick=\"location.reload();\">Reintentar</button>");
|
||||
html.AppendLine(" <button type=\"button\" id=\"sigBtnReportar\" class=\"sig-btn sig-btn-danger\" onclick=\"sigReportarError();\">Reportar a soporte</button>");
|
||||
html.AppendLine(" </div>");
|
||||
html.AppendLine(" </div>");
|
||||
html.AppendLine(" </section>");
|
||||
html.AppendLine("</main>");
|
||||
|
||||
html.AppendLine("<script>");
|
||||
html.AppendLine("var sigCveUsuarioSesion='" + cveUsuarioJavaScript + "';");
|
||||
html.AppendLine("var sigCodigoError='" + codigoJavaScript + "';");
|
||||
html.AppendLine("var sigUrlReporte='" + HttpUtility.JavaScriptStringEncode(urlReporte) + "';");
|
||||
html.AppendLine("var sigMenuStorageKey='sig.currentMenu.v1';");
|
||||
|
||||
html.AppendLine("function sigBuscarSubMenu(elemento){");
|
||||
html.AppendLine(" var hijos=elemento.children;");
|
||||
html.AppendLine(" for(var i=0;i<hijos.length;i++){");
|
||||
html.AppendLine(" if(hijos[i].tagName==='UL' && (' '+hijos[i].className+' ').indexOf(' sub-menu ')>=0){return hijos[i];}");
|
||||
html.AppendLine(" }");
|
||||
html.AppendLine(" return null;");
|
||||
html.AppendLine("}");
|
||||
|
||||
html.AppendLine("function sigInicializarMenu(menu){");
|
||||
html.AppendLine(" menu.onclick=function(evento){");
|
||||
html.AppendLine(" evento=evento||window.event;");
|
||||
html.AppendLine(" var enlace=evento.target||evento.srcElement;");
|
||||
html.AppendLine(" while(enlace&&enlace!==menu&&enlace.tagName!=='A'){enlace=enlace.parentNode;}");
|
||||
html.AppendLine(" if(!enlace||enlace===menu){return;}");
|
||||
html.AppendLine(" var li=enlace.parentNode;");
|
||||
html.AppendLine(" var subMenu=sigBuscarSubMenu(li);");
|
||||
html.AppendLine(" if(!subMenu){return;}");
|
||||
html.AppendLine(" if(evento.preventDefault){evento.preventDefault();}else{evento.returnValue=false;}");
|
||||
html.AppendLine(" var abierto=(' '+li.className+' ').indexOf(' open ')>=0;");
|
||||
html.AppendLine(" li.className=abierto?li.className.replace(/\\bopen\\b/g,'').replace(/^\\s+|\\s+$/g,''):(li.className+' open').replace(/^\\s+|\\s+$/g,'');");
|
||||
html.AppendLine(" };");
|
||||
html.AppendLine("}");
|
||||
|
||||
html.AppendLine("function sigRestaurarMenu(){");
|
||||
html.AppendLine(" if(!sigCveUsuarioSesion){return;}");
|
||||
html.AppendLine(" try{");
|
||||
html.AppendLine(" var contenido=localStorage.getItem(sigMenuStorageKey);");
|
||||
html.AppendLine(" if(!contenido){return;}");
|
||||
html.AppendLine(" var datos=JSON.parse(contenido);");
|
||||
html.AppendLine(" if(!datos||!datos.menuHtml||!datos.cveUsuario){return;}");
|
||||
html.AppendLine(" if(String(datos.cveUsuario)!==String(sigCveUsuarioSesion)){return;}");
|
||||
html.AppendLine(" var menu=document.getElementById('sigMenuRestaurado');");
|
||||
html.AppendLine(" if(!menu){return;}");
|
||||
html.AppendLine(" menu.innerHTML=datos.menuHtml;");
|
||||
html.AppendLine(" sigInicializarMenu(menu);");
|
||||
html.AppendLine(" }catch(ex){if(window.console&&console.warn){console.warn('No fue posible restaurar el menu de SIG.',ex);}}");
|
||||
html.AppendLine("}");
|
||||
|
||||
html.AppendLine("function sigCopiarCodigo(){");
|
||||
html.AppendLine(" if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(sigCodigoError);}else{");
|
||||
html.AppendLine(" var temporal=document.createElement('textarea');");
|
||||
html.AppendLine(" temporal.value=sigCodigoError;");
|
||||
html.AppendLine(" document.body.appendChild(temporal);");
|
||||
html.AppendLine(" temporal.select();");
|
||||
html.AppendLine(" document.execCommand('copy');");
|
||||
html.AppendLine(" document.body.removeChild(temporal);");
|
||||
html.AppendLine(" }");
|
||||
html.AppendLine(" document.getElementById('sigResultadoReporte').textContent='El codigo fue copiado.';");
|
||||
html.AppendLine("}");
|
||||
|
||||
html.AppendLine("function sigReportarError(){");
|
||||
html.AppendLine(" var boton=document.getElementById('sigBtnReportar');");
|
||||
html.AppendLine(" var resultado=document.getElementById('sigResultadoReporte');");
|
||||
html.AppendLine(" boton.disabled=true;");
|
||||
html.AppendLine(" resultado.textContent='Enviando reporte...';");
|
||||
html.AppendLine(" var xhr=new XMLHttpRequest();");
|
||||
html.AppendLine(" xhr.open('POST',sigUrlReporte,true);");
|
||||
html.AppendLine(" xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=UTF-8');");
|
||||
html.AppendLine(" xhr.onreadystatechange=function(){");
|
||||
html.AppendLine(" if(xhr.readyState!==4){return;}");
|
||||
html.AppendLine(" if(xhr.status>=200&&xhr.status<300){resultado.textContent='El error fue reportado correctamente.';return;}");
|
||||
html.AppendLine(" boton.disabled=false;");
|
||||
html.AppendLine(" resultado.textContent='No fue posible enviar el reporte. Comunica el codigo a soporte.';");
|
||||
html.AppendLine(" };");
|
||||
html.AppendLine(" xhr.send('codigo='+encodeURIComponent(sigCodigoError)+'&ruta='+encodeURIComponent(window.location.href));");
|
||||
html.AppendLine("}");
|
||||
|
||||
html.AppendLine("sigRestaurarMenu();");
|
||||
html.AppendLine("</script>");
|
||||
html.AppendLine("</body>");
|
||||
html.AppendLine("</html>");
|
||||
|
||||
return html.ToString();
|
||||
}
|
||||
|
||||
public static string ObtenerDetalleTecnico(
|
||||
Exception exception)
|
||||
{
|
||||
if (exception == null)
|
||||
{
|
||||
return "No se encontro informacion de la excepcion.";
|
||||
}
|
||||
|
||||
StringBuilder texto = new StringBuilder();
|
||||
Exception actual = exception;
|
||||
int nivel = 1;
|
||||
|
||||
while (actual != null)
|
||||
{
|
||||
texto.AppendLine(
|
||||
"EXCEPCION " + nivel);
|
||||
|
||||
texto.AppendLine(
|
||||
"Tipo: " + actual.GetType().FullName);
|
||||
|
||||
texto.AppendLine(
|
||||
"Mensaje: " + actual.Message);
|
||||
|
||||
HttpParseException parseException =
|
||||
actual as HttpParseException;
|
||||
|
||||
if (parseException != null)
|
||||
{
|
||||
texto.AppendLine(
|
||||
"Archivo: " + parseException.VirtualPath);
|
||||
|
||||
texto.AppendLine(
|
||||
"Linea: " + parseException.Line);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(
|
||||
actual.StackTrace))
|
||||
{
|
||||
texto.AppendLine();
|
||||
texto.AppendLine("Stack trace:");
|
||||
texto.AppendLine(actual.StackTrace);
|
||||
}
|
||||
|
||||
texto.AppendLine();
|
||||
texto.AppendLine(
|
||||
"--------------------------------------------------");
|
||||
texto.AppendLine();
|
||||
|
||||
actual = actual.InnerException;
|
||||
nivel++;
|
||||
}
|
||||
|
||||
return texto.ToString();
|
||||
}
|
||||
|
||||
private static string ObtenerCodigoExistente(
|
||||
Exception exception)
|
||||
{
|
||||
Exception actual = exception;
|
||||
|
||||
while (actual != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
object valor =
|
||||
actual.Data[ErrorCodeDataKey];
|
||||
|
||||
if (valor != null)
|
||||
{
|
||||
return valor.ToString();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
actual = actual.InnerException;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static void AsignarCodigo(
|
||||
Exception exception,
|
||||
string codigo)
|
||||
{
|
||||
Exception actual = exception;
|
||||
|
||||
while (actual != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
actual.Data[ErrorCodeDataKey] = codigo;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
actual = actual.InnerException;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
106
SIG.Web/AppCode/Services/Logging/ExceptionLogger.cs
Normal file
106
SIG.Web/AppCode/Services/Logging/ExceptionLogger.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
|
||||
namespace SIG.Web.AppCode.Services.Logging
|
||||
{
|
||||
public static class ExceptionLogger
|
||||
{
|
||||
private static readonly object LockObject = new object();
|
||||
|
||||
public static void Log(Exception exception, string source)
|
||||
{
|
||||
Log(exception, source, null);
|
||||
}
|
||||
|
||||
public static void Log(Exception exception, string source, string userMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (exception == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string logPath = GetLogPath();
|
||||
string directory = Path.GetDirectoryName(logPath);
|
||||
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
string content = BuildLogContent(exception, source, userMessage);
|
||||
|
||||
lock (LockObject)
|
||||
{
|
||||
File.AppendAllText(logPath, content, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Never throw from the logger.
|
||||
// If the logger fails, the application must continue.
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetLogPath()
|
||||
{
|
||||
string configuredPath = ConfigurationManager.AppSettings["ExceptionLogFilePath"];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(configuredPath))
|
||||
{
|
||||
configuredPath = "~/log/log.txt";
|
||||
}
|
||||
|
||||
if (HttpContext.Current != null)
|
||||
{
|
||||
return HttpContext.Current.Server.MapPath(configuredPath);
|
||||
}
|
||||
|
||||
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log", "log.txt");
|
||||
}
|
||||
|
||||
private static string BuildLogContent(Exception exception, string source, string userMessage)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("============================================================");
|
||||
sb.AppendLine("Date: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
|
||||
sb.AppendLine("Source: " + Safe(source));
|
||||
sb.AppendLine("UserMessage: " + Safe(userMessage));
|
||||
|
||||
HttpContext context = HttpContext.Current;
|
||||
|
||||
if (context != null && context.Request != null)
|
||||
{
|
||||
sb.AppendLine("Url: " + Safe(context.Request.Url != null ? context.Request.Url.ToString() : null));
|
||||
sb.AppendLine("HttpMethod: " + Safe(context.Request.HttpMethod));
|
||||
sb.AppendLine("DireccionIP: " + Safe(context.Request.UserHostAddress));
|
||||
sb.AppendLine("AgenteUsuario: " + Safe(context.Request.UserAgent));
|
||||
}
|
||||
|
||||
if (context != null && context.User != null && context.User.Identity != null)
|
||||
{
|
||||
sb.AppendLine("Identity: " + Safe(context.User.Identity.Name));
|
||||
sb.AppendLine("IsAuthenticated: " + context.User.Identity.IsAuthenticated);
|
||||
}
|
||||
|
||||
sb.AppendLine("ExceptionType: " + exception.GetType().FullName);
|
||||
sb.AppendLine("Message: " + exception.Message);
|
||||
sb.AppendLine("BaseException: " + exception.GetBaseException().Message);
|
||||
sb.AppendLine("StackTrace:");
|
||||
sb.AppendLine(exception.ToString());
|
||||
sb.AppendLine();
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string Safe(string value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? string.Empty : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
SIG.Web/AppCode/Services/Login/DummyLoginService.cs
Normal file
31
SIG.Web/AppCode/Services/Login/DummyLoginService.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using SIG.Web.AppCode.Contracts.Login;
|
||||
using SIG.Web.AppCode.Session;
|
||||
|
||||
namespace SIG.Web.AppCode.Services.Login
|
||||
{
|
||||
public sealed class DummyLoginService : ILoginService
|
||||
{
|
||||
public LoginResult Login(string nombreUsuario, string contrasena, string nombreDominio)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nombreUsuario))
|
||||
{
|
||||
return LoginResult.Fail("Escriba un nombre de usuario.");
|
||||
}
|
||||
|
||||
return LoginResult.Ok(new UserSessionModel
|
||||
{
|
||||
CveUsuario = 15,
|
||||
CveRol = 1,
|
||||
CveEmpresa = 1,
|
||||
NombreUsuario = nombreUsuario.Trim(),
|
||||
Nombre = "Usuario Demo",
|
||||
CorreoElectronico = "usuario.demo@example.invalid",
|
||||
NombreRol = "Administrador",
|
||||
NombreDominio = nombreDominio,
|
||||
TokenSesion = Guid.NewGuid().ToString("N"),
|
||||
DebeCambiarContrasena = false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
21
SIG.Web/AppCode/Services/Menu/DummyMenuProvider.cs
Normal file
21
SIG.Web/AppCode/Services/Menu/DummyMenuProvider.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using SIG.Web.AppCode.Session;
|
||||
|
||||
namespace SIG.Web.AppCode.Services.Menu
|
||||
{
|
||||
public sealed class DummyMenuProvider : IMenuProvider
|
||||
{
|
||||
public IReadOnlyList<MenuItemModel> GetMenu(UserSessionModel user)
|
||||
{
|
||||
return new List<MenuItemModel>
|
||||
{
|
||||
new MenuItemModel { NombreModulo = "Home", Url = "~/Inicio.aspx", ClaseCssIcono = "icon-home" },
|
||||
new MenuItemModel { NombreModulo = "Developer tools", Url = "#", ClaseCssIcono = "icon-puzzle", Hijos =
|
||||
{
|
||||
new MenuItemModel { NombreModulo = "Blank page", Url = "~/Inicio.aspx", ClaseCssIcono = "icon-doc" }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
85
SIG.Web/AppCode/Services/Menu/MenuProvider.cs
Normal file
85
SIG.Web/AppCode/Services/Menu/MenuProvider.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using SIG.Web.AppCode.Contracts.Database;
|
||||
using SIG.Web.AppCode.Services.Database;
|
||||
using SIG.Web.AppCode.Utilities;
|
||||
using SIG.Web.AppCode.Session;
|
||||
|
||||
namespace SIG.Web.AppCode.Services.Menu
|
||||
{
|
||||
public sealed class MenuProvider : IMenuProvider
|
||||
{
|
||||
private readonly IDbExecutor _dbExecutor;
|
||||
|
||||
public MenuProvider(IDbExecutor dbExecutor)
|
||||
{
|
||||
_dbExecutor = dbExecutor;
|
||||
}
|
||||
|
||||
public IReadOnlyList<MenuItemModel> GetMenu(UserSessionModel user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
return new List<MenuItemModel>();
|
||||
}
|
||||
|
||||
var modulesResult = GetModules(user);
|
||||
|
||||
if (!modulesResult.Success || modulesResult.Data == null)
|
||||
{
|
||||
return new List<MenuItemModel>();
|
||||
}
|
||||
|
||||
return BuildTree(modulesResult.Data);
|
||||
}
|
||||
|
||||
private SIG.Web.AppCode.Models.Common.DbResult<List<MenuItemModel>> GetModules(UserSessionModel user)
|
||||
{
|
||||
return _dbExecutor.Execute(user.NombreDominio, connection =>
|
||||
{
|
||||
using (IDbCommand command = connection.CreateCommand())
|
||||
{
|
||||
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_MenuList", user.CveUsuario);
|
||||
StoredProcedureCommand.AddInput(command, "@CveRol", user.CveRol, DbType.Int32);
|
||||
|
||||
List<MenuItemModel> items;
|
||||
|
||||
using (IDataReader reader = command.ExecuteReader())
|
||||
{
|
||||
items = reader.ToList<MenuItemModel>();
|
||||
}
|
||||
|
||||
StoredProcedureCommand.EnsureSuccess(command);
|
||||
return items;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static IReadOnlyList<MenuItemModel> BuildTree(List<MenuItemModel> flatItems)
|
||||
{
|
||||
if (flatItems == null || flatItems.Count == 0)
|
||||
{
|
||||
return new List<MenuItemModel>();
|
||||
}
|
||||
|
||||
var byId = flatItems.ToDictionary(x => x.CveModulo);
|
||||
var roots = new List<MenuItemModel>();
|
||||
|
||||
foreach (var item in flatItems.OrderBy(x => x.OrdenVisualizacion).ThenBy(x => x.NombreModulo))
|
||||
{
|
||||
if (item.CveModuloPadre.HasValue && byId.ContainsKey(item.CveModuloPadre.Value))
|
||||
{
|
||||
byId[item.CveModuloPadre.Value].Hijos.Add(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
roots.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return roots;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using SIG.Web.AppCode.Contracts.Search;
|
||||
using SIG.Web.AppCode.Session;
|
||||
|
||||
namespace SIG.Web.AppCode.Services.Search
|
||||
{
|
||||
public sealed class DummyBuscadorCatalogoDataProvider : IBuscadorCatalogoDataProvider
|
||||
{
|
||||
public BuscadorCatalogoCallbackRespuesta Search(BuscadorCatalogoCallbackSolicitud request, UserSessionModel user)
|
||||
{
|
||||
var response = new BuscadorCatalogoCallbackRespuesta { Total = 1 };
|
||||
response.Rows.Add(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = 1,
|
||||
["Descripcion"] = "Dummy result"
|
||||
});
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
118
SIG.Web/AppCode/Services/Security/LoginService.cs
Normal file
118
SIG.Web/AppCode/Services/Security/LoginService.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using SIG.Web.AppCode.Contracts.Security;
|
||||
using SIG.Web.AppCode.Models.Security;
|
||||
using SIG.Web.AppCode.Session;
|
||||
|
||||
namespace SIG.Web.AppCode.Services.Security
|
||||
{
|
||||
public sealed class LoginService : ILoginService
|
||||
{
|
||||
private const string GenericError = "No fue posible iniciar sesion. Verifique sus datos.";
|
||||
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IPasswordHasher _passwordHasher;
|
||||
private readonly ILoginAttemptRepository _loginAttemptRepository;
|
||||
private readonly IUserSessionRepository _userSessionRepository;
|
||||
|
||||
public LoginService(
|
||||
IUserRepository userRepository,
|
||||
IPasswordHasher passwordHasher,
|
||||
ILoginAttemptRepository loginAttemptRepository,
|
||||
IUserSessionRepository userSessionRepository)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
_passwordHasher = passwordHasher;
|
||||
_loginAttemptRepository = loginAttemptRepository;
|
||||
_userSessionRepository = userSessionRepository;
|
||||
}
|
||||
|
||||
public LoginResult Login(LoginRequest request)
|
||||
{
|
||||
if (request == null || string.IsNullOrWhiteSpace(request.NombreUsuario) || string.IsNullOrWhiteSpace(request.Contrasena))
|
||||
{
|
||||
return LoginResult.Fail(GenericError);
|
||||
}
|
||||
|
||||
string username = request.NombreUsuario.Trim();
|
||||
var userResult = _userRepository.ObtenerPorNombreUsuario(username, request.NombreDominio);
|
||||
|
||||
if (!userResult.Success)
|
||||
{
|
||||
return LoginResult.Fail(userResult.UserMessage);
|
||||
}
|
||||
|
||||
UserModel user = userResult.Data;
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
_loginAttemptRepository.Save(null, username, false, "UserNotFound", request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
|
||||
return LoginResult.Fail(GenericError);
|
||||
}
|
||||
|
||||
if (!user.CorreoVerificado)
|
||||
{
|
||||
_loginAttemptRepository.Save(user.CveUsuario, username, false, "EmailNotVerified", request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
|
||||
return LoginResult.Fail("Debes verificar tu correo electrónico antes de iniciar sesión.");
|
||||
}
|
||||
|
||||
if (!user.Activo)
|
||||
{
|
||||
_loginAttemptRepository.Save(user.CveUsuario, username, false, "InactiveUser", request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
|
||||
return LoginResult.Fail("El usuario se encuentra inactivo.");
|
||||
}
|
||||
|
||||
if (user.BloqueadoHasta.HasValue && user.BloqueadoHasta.Value > DateTime.Now) {
|
||||
_loginAttemptRepository.Save(user.CveUsuario, username, false, "LockedUser", request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
|
||||
return LoginResult.Fail("El usuario se encuentra bloqueado temporalmente.");
|
||||
}
|
||||
|
||||
if (!_passwordHasher.VerifyPassword(request.Contrasena, user.HashContrasena))
|
||||
{
|
||||
_userRepository.RegisterFailedLogin(user.CveUsuario, request.NombreDominio);
|
||||
_loginAttemptRepository.Save(user.CveUsuario, username, false, "InvalidPassword", request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
|
||||
return LoginResult.Fail(GenericError);
|
||||
}
|
||||
|
||||
string sessionToken = Guid.NewGuid().ToString("N");
|
||||
|
||||
var loginUpdateResult = _userRepository.RegisterSuccessfulLogin(user.CveUsuario, request.NombreDominio);
|
||||
|
||||
if (!loginUpdateResult.Success)
|
||||
{
|
||||
return LoginResult.Fail(loginUpdateResult.UserMessage);
|
||||
}
|
||||
|
||||
var sessionResult = _userSessionRepository.StartSession(user.CveUsuario, sessionToken, request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
|
||||
|
||||
if (!sessionResult.Success)
|
||||
{
|
||||
return LoginResult.Fail(sessionResult.UserMessage);
|
||||
}
|
||||
|
||||
_loginAttemptRepository.Save(user.CveUsuario, username, true, null, request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
|
||||
|
||||
var sessionUser = new UserSessionModel
|
||||
{
|
||||
CveUsuario = user.CveUsuario,
|
||||
CveRol = user.CveRol,
|
||||
NombreUsuario = user.NombreUsuario,
|
||||
Nombre = user.NombreUsuario,
|
||||
CorreoElectronico = user.CorreoElectronico,
|
||||
NombreRol = user.NombreRol,
|
||||
NombreDominio = request.NombreDominio,
|
||||
TokenSesion = sessionToken,
|
||||
DebeCambiarContrasena = user.DebeCambiarContrasena
|
||||
};
|
||||
|
||||
return LoginResult.Ok(sessionUser);
|
||||
}
|
||||
|
||||
public void Logout(string sessionToken, string domainName)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(sessionToken))
|
||||
{
|
||||
_userSessionRepository.EndSession(sessionToken, domainName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
SIG.Web/AppCode/Services/Security/PasswordHasher.cs
Normal file
40
SIG.Web/AppCode/Services/Security/PasswordHasher.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using SIG.Web.AppCode.Contracts.Security;
|
||||
namespace SIG.Web.AppCode.Services.Security
|
||||
{
|
||||
public sealed class PasswordHasher : IPasswordHasher
|
||||
{
|
||||
private const int SaltSize = 16;
|
||||
private const int HashSize = 32;
|
||||
private const int Iterations = 100000;
|
||||
public string HashPassword(string password)
|
||||
{
|
||||
byte[] salt = new byte[SaltSize];
|
||||
using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(salt);
|
||||
byte[] hash = Derive(password, salt, Iterations);
|
||||
return "PBKDF2-SHA256$" + Iterations + "$" + Convert.ToBase64String(salt) + "$" + Convert.ToBase64String(hash);
|
||||
}
|
||||
public bool VerifyPassword(string password, string storedHash)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(storedHash)) return false;
|
||||
string[] parts = storedHash.Split('$');
|
||||
if (parts.Length != 4 || parts[0] != "PBKDF2-SHA256") return false;
|
||||
int iterations; if (!int.TryParse(parts[1], out iterations)) return false;
|
||||
byte[] salt = Convert.FromBase64String(parts[2]);
|
||||
byte[] expected = Convert.FromBase64String(parts[3]);
|
||||
byte[] actual = Derive(password, salt, iterations);
|
||||
return FixedTimeEquals(actual, expected);
|
||||
}
|
||||
private static byte[] Derive(string password, byte[] salt, int iterations)
|
||||
{
|
||||
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256)) return pbkdf2.GetBytes(HashSize);
|
||||
}
|
||||
private static bool FixedTimeEquals(byte[] left, byte[] right)
|
||||
{
|
||||
if (left == null || right == null || left.Length != right.Length) return false;
|
||||
int diff = 0; for (int i = 0; i < left.Length; i++) diff |= left[i] ^ right[i];
|
||||
return diff == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
101
SIG.Web/AppCode/Services/Security/PermissionService.cs
Normal file
101
SIG.Web/AppCode/Services/Security/PermissionService.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using SIG.Web.AppCode.Contracts.Database;
|
||||
using SIG.Web.AppCode.Contracts.Security;
|
||||
using SIG.Web.AppCode.Services.Database;
|
||||
|
||||
namespace SIG.Web.AppCode.Services.Security
|
||||
{
|
||||
public sealed class PermissionService : IPermissionService
|
||||
{
|
||||
private readonly IDbExecutor _dbExecutor;
|
||||
|
||||
public PermissionService(IDbExecutor dbExecutor)
|
||||
{
|
||||
_dbExecutor = dbExecutor;
|
||||
}
|
||||
|
||||
public bool HasPermission(int cveUser, string permissionCode)
|
||||
{
|
||||
if (cveUser <= 0 || string.IsNullOrWhiteSpace(permissionCode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = _dbExecutor.Execute(null, connection =>
|
||||
{
|
||||
using (IDbCommand command = connection.CreateCommand())
|
||||
{
|
||||
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_PermisoCheck", cveUser);
|
||||
StoredProcedureCommand.AddInput(command, "@CodigoPermiso", permissionCode.Trim(), DbType.String);
|
||||
|
||||
object value = command.ExecuteScalar();
|
||||
StoredProcedureCommand.EnsureSuccess(command);
|
||||
return value != null && value != DBNull.Value && Convert.ToBoolean(value);
|
||||
}
|
||||
});
|
||||
|
||||
return result.Success && result.Data;
|
||||
}
|
||||
|
||||
public bool CanAccessPage(int cveUser, string pageUrl)
|
||||
{
|
||||
if (cveUser <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string normalizedUrl = NormalizePageUrl(pageUrl);
|
||||
|
||||
if (IsPublicPage(normalizedUrl))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var result = _dbExecutor.Execute(null, connection =>
|
||||
{
|
||||
using (IDbCommand command = connection.CreateCommand())
|
||||
{
|
||||
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_PaginaAccessCheck", cveUser);
|
||||
StoredProcedureCommand.AddInput(command, "@PaginaUrl", normalizedUrl, DbType.String);
|
||||
|
||||
object value = command.ExecuteScalar();
|
||||
StoredProcedureCommand.EnsureSuccess(command);
|
||||
return value != null && value != DBNull.Value && Convert.ToBoolean(value);
|
||||
}
|
||||
});
|
||||
|
||||
return result.Success && result.Data;
|
||||
}
|
||||
|
||||
private static string NormalizePageUrl(string pageUrl)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pageUrl))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string value = pageUrl.Trim();
|
||||
|
||||
if (value.StartsWith("~/", StringComparison.Ordinal))
|
||||
{
|
||||
value = value.Substring(2);
|
||||
}
|
||||
|
||||
if (value.StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
value = value.Substring(1);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static bool IsPublicPage(string normalizedUrl)
|
||||
{
|
||||
return normalizedUrl.Equals("Login.aspx", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalizedUrl.Equals("Logout.aspx", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalizedUrl.Equals("AccessDenied.aspx", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalizedUrl.Equals("Error.aspx", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using SIG.Web.AppCode.Contracts.Security;
|
||||
using SIG.Web.AppCode.Models.Common;
|
||||
using SIG.Web.AppCode.Models.Security;
|
||||
|
||||
namespace SIG.Web.AppCode.Services.Security
|
||||
{
|
||||
public sealed class PublicRegistrationService : IPublicRegistrationService
|
||||
{
|
||||
private readonly IUserAdministrationRepository _repository;
|
||||
private readonly IPasswordHasher _passwordHasher;
|
||||
private readonly IEmailSender _emailSender;
|
||||
public PublicRegistrationService(IUserAdministrationRepository repository, IPasswordHasher passwordHasher, IEmailSender emailSender) { _repository = repository; _passwordHasher = passwordHasher; _emailSender = emailSender; }
|
||||
|
||||
public DbResult<bool> Validate(PublicRegistrationDraft draft, string domainName)
|
||||
{
|
||||
string error = ValidateRequired(draft);
|
||||
return error == null ? _repository.ValidateRegistration(draft.NombreUsuario, draft.CorreoElectronico, draft.RFC, domainName) : DbResult<bool>.Fail(error, error, null);
|
||||
}
|
||||
|
||||
public DbResult<List<StateOptionModel>> ListStates(string domainName) { return _repository.ListStates(domainName); }
|
||||
|
||||
public DbResult<bool> Register(PublicRegistrationDraft draft, string verificationBaseUrl, string domainName)
|
||||
{
|
||||
DbResult<bool> validation = Validate(draft, domainName); if (!validation.Success) return validation;
|
||||
if (!_emailSender.IsConfigured) return DbResult<bool>.Fail("El envío de correo todavía no está configurado. Contacta a Sistemas.", "SMTP no configurado.", null);
|
||||
string token = CreateToken();
|
||||
DbResult<RegistrationCreatedModel> created = _repository.CreateRegistration(draft, _passwordHasher.HashPassword(draft.Contrasena), HashToken(token), domainName);
|
||||
if (!created.Success) return DbResult<bool>.Fail(created.UserMessage, created.TechnicalMessage, created.Exception);
|
||||
try
|
||||
{
|
||||
_emailSender.SendVerification(draft.CorreoElectronico, verificationBaseUrl + (verificationBaseUrl.Contains("?") ? "&" : "?") + "token=" + Uri.EscapeDataString(token));
|
||||
return DbResult<bool>.Ok(true);
|
||||
}
|
||||
catch (Exception ex) { return DbResult<bool>.Fail("La cuenta quedó pendiente, pero no fue posible enviar el correo. Contacta a Sistemas para reenviarlo.", ex.Message, ex); }
|
||||
}
|
||||
|
||||
public DbResult<bool> Verify(string token, string domainName)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(token) ? DbResult<bool>.Fail("La liga de verificación no es válida.", "Token vacío.", null) : _repository.VerifyEmail(HashToken(token), domainName);
|
||||
}
|
||||
|
||||
private static string ValidateRequired(PublicRegistrationDraft d)
|
||||
{
|
||||
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.";
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string CreateToken() { byte[] bytes = new byte[32]; using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(bytes); return Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_'); }
|
||||
private static string HashToken(string token) { using (SHA256 sha = SHA256.Create()) { byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(token)); var value = new StringBuilder(64); foreach (byte item in hash) value.Append(item.ToString("x2")); return value.ToString(); } }
|
||||
}
|
||||
}
|
||||
31
SIG.Web/AppCode/Services/Security/SmtpEmailSender.cs
Normal file
31
SIG.Web/AppCode/Services/Security/SmtpEmailSender.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
using SIG.Web.AppCode.Contracts.Security;
|
||||
|
||||
namespace SIG.Web.AppCode.Services.Security
|
||||
{
|
||||
public sealed class SmtpEmailSender : IEmailSender
|
||||
{
|
||||
public bool IsConfigured { get { return !string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["SmtpHost"]) && !string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["EmailFrom"]); } }
|
||||
|
||||
public void SendVerification(string recipient, string verificationUrl)
|
||||
{
|
||||
if (!IsConfigured) throw new InvalidOperationException("El envío de correo no se encuentra configurado.");
|
||||
int port; if (!int.TryParse(ConfigurationManager.AppSettings["SmtpPort"], out port)) port = 25;
|
||||
bool ssl; bool.TryParse(ConfigurationManager.AppSettings["SmtpEnableSsl"], out ssl);
|
||||
using (var message = new MailMessage(ConfigurationManager.AppSettings["EmailFrom"], recipient))
|
||||
using (var client = new SmtpClient(ConfigurationManager.AppSettings["SmtpHost"], port))
|
||||
{
|
||||
message.Subject = "Verifica tu correo - Plataforma de Desarrolladores";
|
||||
message.IsBodyHtml = true;
|
||||
message.Body = "<p>Para concluir la creación de tu cuenta, abre la siguiente liga:</p><p><a href=\"" + WebUtility.HtmlEncode(verificationUrl) + "\">Verificar mi correo</a></p><p>La liga vence en 24 horas.</p>";
|
||||
client.EnableSsl = ssl;
|
||||
string user = ConfigurationManager.AppSettings["SmtpUser"];
|
||||
if (!string.IsNullOrWhiteSpace(user)) client.Credentials = new NetworkCredential(user, ConfigurationManager.AppSettings["SmtpPassword"]);
|
||||
client.Send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Web;
|
||||
using SIG.Web.AppCode.Contracts.Security;
|
||||
using SIG.Web.AppCode.Session;
|
||||
namespace SIG.Web.AppCode.Services.Security
|
||||
{
|
||||
public sealed class WebFormsUserContextProvider : Contracts.Security.IUserContextProvider
|
||||
{
|
||||
public bool IsAuthenticated() { return GetCurrentUser() != null; }
|
||||
public UserSessionModel GetCurrentUser()
|
||||
{
|
||||
return HttpContext.Current == null || HttpContext.Current.Session == null ? null : HttpContext.Current.Session[SessionKeys.CurrentUser] as UserSessionModel;
|
||||
}
|
||||
public void SetCurrentUser(UserSessionModel user)
|
||||
{
|
||||
if (HttpContext.Current == null || HttpContext.Current.Session == null)
|
||||
return; HttpContext.Current.Session[SessionKeys.CurrentUser] = user;
|
||||
HttpContext.Current.Session[SessionKeys.TokenSesion] = user != null ? user.TokenSesion : null;
|
||||
}
|
||||
public void Clear()
|
||||
{
|
||||
if (HttpContext.Current == null || HttpContext.Current.Session == null)
|
||||
return; HttpContext.Current.Session.Remove(SessionKeys.CurrentUser);
|
||||
HttpContext.Current.Session.Remove(SessionKeys.TokenSesion);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
SIG.Web/AppCode/Session/IMenuProvider.cs
Normal file
9
SIG.Web/AppCode/Session/IMenuProvider.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SIG.Web.AppCode.Session
|
||||
{
|
||||
public interface IMenuProvider
|
||||
{
|
||||
IReadOnlyList<MenuItemModel> GetMenu(UserSessionModel user);
|
||||
}
|
||||
}
|
||||
10
SIG.Web/AppCode/Session/ISessionContext.cs
Normal file
10
SIG.Web/AppCode/Session/ISessionContext.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace SIG.Web.AppCode.Session
|
||||
{
|
||||
public interface ISessionContext
|
||||
{
|
||||
T Get<T>(string key) where T : class;
|
||||
void Set<T>(string key, T value) where T : class;
|
||||
void Remove(string key);
|
||||
void Clear();
|
||||
}
|
||||
}
|
||||
19
SIG.Web/AppCode/Session/MenuItemModel.cs
Normal file
19
SIG.Web/AppCode/Session/MenuItemModel.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SIG.Web.AppCode.Session
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class MenuItemModel
|
||||
{
|
||||
public int CveModulo { get; set; }
|
||||
public int? CveModuloPadre { get; set; }
|
||||
public string NombreModulo { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string ClaseCssIcono { get; set; }
|
||||
public string ClaseCssEstilo { get; set; }
|
||||
public int OrdenVisualizacion { get; set; }
|
||||
public bool Visible { get; set; } = true;
|
||||
public List<MenuItemModel> Hijos { get; set; } = new List<MenuItemModel>();
|
||||
}
|
||||
}
|
||||
8
SIG.Web/AppCode/Session/SessionKeys.cs
Normal file
8
SIG.Web/AppCode/Session/SessionKeys.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace SIG.Web.AppCode.Session
|
||||
{
|
||||
public static class SessionKeys
|
||||
{
|
||||
public const string CurrentUser = "SIG_CurrentUser";
|
||||
public const string TokenSesion = "SIG_SessionToken";
|
||||
}
|
||||
}
|
||||
16
SIG.Web/AppCode/Session/UserSessionModel.cs
Normal file
16
SIG.Web/AppCode/Session/UserSessionModel.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace SIG.Web.AppCode.Session
|
||||
{
|
||||
public sealed class UserSessionModel
|
||||
{
|
||||
public int CveUsuario { get; set; }
|
||||
public int CveRol { get; set; }
|
||||
public int CveEmpresa { get; set; }
|
||||
public string NombreUsuario { get; set; }
|
||||
public string Nombre { get; set; }
|
||||
public string CorreoElectronico { get; set; }
|
||||
public string NombreRol { get; set; }
|
||||
public string NombreDominio { get; set; }
|
||||
public string TokenSesion { get; set; }
|
||||
public bool DebeCambiarContrasena { get; set; }
|
||||
}
|
||||
}
|
||||
28
SIG.Web/AppCode/Session/WebFormsSessionContext.cs
Normal file
28
SIG.Web/AppCode/Session/WebFormsSessionContext.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Web;
|
||||
|
||||
namespace SIG.Web.AppCode.Session
|
||||
{
|
||||
public sealed class WebFormsSessionContext : ISessionContext
|
||||
{
|
||||
public T Get<T>(string key) where T : class
|
||||
{
|
||||
return HttpContext.Current?.Session?[key] as T;
|
||||
}
|
||||
|
||||
public void Set<T>(string key, T value) where T : class
|
||||
{
|
||||
if (HttpContext.Current?.Session == null) return;
|
||||
HttpContext.Current.Session[key] = value;
|
||||
}
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
HttpContext.Current?.Session?.Remove(key);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
HttpContext.Current?.Session?.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
74
SIG.Web/AppCode/SigControls/BuscadorCatalogo.cs
Normal file
74
SIG.Web/AppCode/SigControls/BuscadorCatalogo.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Web.UI;
|
||||
using System.Web.UI.WebControls;
|
||||
using Newtonsoft.Json;
|
||||
using SIG.Web.AppCode.Configuration;
|
||||
using SIG.Web.AppCode.Contracts.Search;
|
||||
|
||||
namespace SIG.Web.AppCode.SigControls
|
||||
{
|
||||
[ToolboxData("<{0}:BuscadorCatalogo runat=server></{0}:BuscadorCatalogo>")]
|
||||
public class BuscadorCatalogo : CompositeControl, INamingContainer
|
||||
{
|
||||
private TextBox _textBox;
|
||||
private HiddenField _value;
|
||||
private Button _button;
|
||||
|
||||
[Category("SIG")]
|
||||
public string Catalogo
|
||||
{
|
||||
get { return ViewState["Catalogo"] as string ?? string.Empty; }
|
||||
set { ViewState["Catalogo"] = value; }
|
||||
}
|
||||
|
||||
[Category("SIG")]
|
||||
public string Text
|
||||
{
|
||||
get { EnsureChildControls(); return _textBox.Text; }
|
||||
set { EnsureChildControls(); _textBox.Text = value; }
|
||||
}
|
||||
|
||||
[Category("SIG")]
|
||||
public string SelectedValue
|
||||
{
|
||||
get { EnsureChildControls(); return _value.Value; }
|
||||
set { EnsureChildControls(); _value.Value = value; }
|
||||
}
|
||||
|
||||
protected override void CreateChildControls()
|
||||
{
|
||||
Controls.Clear();
|
||||
_textBox = new TextBox { ID = "Txt", CssClass = "form-control sig-buscador-input" };
|
||||
_value = new HiddenField { ID = "Hdn" };
|
||||
_button = new Button { ID = "Btn", Text = "...", CssClass = "btn btn-default sig-buscador-button" };
|
||||
_button.OnClientClick = "return false;";
|
||||
Controls.Add(_value);
|
||||
Controls.Add(_textBox);
|
||||
Controls.Add(_button);
|
||||
}
|
||||
|
||||
protected override void Render(HtmlTextWriter writer)
|
||||
{
|
||||
EnsureChildControls();
|
||||
writer.AddAttribute(HtmlTextWriterAttribute.Class, "input-group sig-buscador-catalogo");
|
||||
writer.AddAttribute("data-catalogo", Catalogo ?? string.Empty);
|
||||
writer.RenderBeginTag(HtmlTextWriterTag.Div);
|
||||
_textBox.RenderControl(writer);
|
||||
writer.AddAttribute(HtmlTextWriterAttribute.Class, "input-group-btn");
|
||||
writer.RenderBeginTag(HtmlTextWriterTag.Span);
|
||||
_button.RenderControl(writer);
|
||||
writer.RenderEndTag();
|
||||
_value.RenderControl(writer);
|
||||
writer.RenderEndTag();
|
||||
}
|
||||
|
||||
public static string SearchJson(string json)
|
||||
{
|
||||
var request = JsonConvert.DeserializeObject<BuscadorCatalogoCallbackSolicitud>(json ?? "{}") ?? new BuscadorCatalogoCallbackSolicitud();
|
||||
var user = AppServices.UserContext.GetCurrentUser();
|
||||
var response = AppServices.SearchProvider.Search(request, user);
|
||||
return JsonConvert.SerializeObject(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
SIG.Web/AppCode/SigControls/DropdownGrid.cs
Normal file
82
SIG.Web/AppCode/SigControls/DropdownGrid.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Web.UI;
|
||||
using System.Web.UI.WebControls;
|
||||
|
||||
namespace SIG.Web.AppCode.SigControls
|
||||
{
|
||||
public sealed class DropdownGridColumn
|
||||
{
|
||||
public string DataField { get; set; }
|
||||
public string HeaderText { get; set; }
|
||||
public bool Visible { get; set; } = true;
|
||||
}
|
||||
|
||||
[ToolboxData("<{0}:DropdownGrid runat=server></{0}:DropdownGrid>")]
|
||||
public class DropdownGrid : CompositeControl, INamingContainer
|
||||
{
|
||||
private HiddenField _selectedValues;
|
||||
private TextBox _text;
|
||||
|
||||
public List<DropdownGridColumn> Columns
|
||||
{
|
||||
get { return ViewState["Columns"] as List<DropdownGridColumn> ?? new List<DropdownGridColumn>(); }
|
||||
set { ViewState["Columns"] = value; }
|
||||
}
|
||||
|
||||
[Category("SIG")]
|
||||
public bool Multiple
|
||||
{
|
||||
get { return ViewState["Multiple"] as bool? ?? false; }
|
||||
set { ViewState["Multiple"] = value; }
|
||||
}
|
||||
|
||||
[Category("SIG")]
|
||||
public string SelectedValues
|
||||
{
|
||||
get { EnsureChildControls(); return _selectedValues.Value; }
|
||||
set { EnsureChildControls(); _selectedValues.Value = value; }
|
||||
}
|
||||
|
||||
[Category("SIG")]
|
||||
public string Text
|
||||
{
|
||||
get { EnsureChildControls(); return _text.Text; }
|
||||
set { EnsureChildControls(); _text.Text = value; }
|
||||
}
|
||||
|
||||
protected override void CreateChildControls()
|
||||
{
|
||||
Controls.Clear();
|
||||
_selectedValues = new HiddenField { ID = "HdnValues" };
|
||||
_text = new TextBox { ID = "TxtText", CssClass = "form-control sig-dropdown-grid-text" };
|
||||
Controls.Add(_selectedValues);
|
||||
Controls.Add(_text);
|
||||
}
|
||||
|
||||
protected override void Render(HtmlTextWriter writer)
|
||||
{
|
||||
EnsureChildControls();
|
||||
writer.AddAttribute(HtmlTextWriterAttribute.Class, "sig-dropdown-grid");
|
||||
writer.AddAttribute("data-multiple", Multiple ? "true" : "false");
|
||||
writer.RenderBeginTag(HtmlTextWriterTag.Div);
|
||||
_selectedValues.RenderControl(writer);
|
||||
_text.RenderControl(writer);
|
||||
writer.AddAttribute(HtmlTextWriterAttribute.Class, "sig-dropdown-grid-popup");
|
||||
writer.RenderBeginTag(HtmlTextWriterTag.Div);
|
||||
writer.Write("<!-- grid popup rendered by JavaScript -->");
|
||||
writer.RenderEndTag();
|
||||
writer.RenderEndTag();
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> GetSelectedValueList()
|
||||
{
|
||||
return (SelectedValues ?? string.Empty)
|
||||
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim())
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
14
SIG.Web/AppCode/SigControls/FiltroActivo.cs
Normal file
14
SIG.Web/AppCode/SigControls/FiltroActivo.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace SIG.Web.AppCode.SigControls
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class FiltroActivo
|
||||
{
|
||||
public string Nombre { get; set; }
|
||||
public string Titulo { get; set; }
|
||||
public string Descripcion { get; set; }
|
||||
public int Orden { get; set; }
|
||||
public bool Visible { get; set; } = true;
|
||||
}
|
||||
}
|
||||
9
SIG.Web/AppCode/SigControls/PDFViewer/PdfClientAction.cs
Normal file
9
SIG.Web/AppCode/SigControls/PDFViewer/PdfClientAction.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace SIG.Web.AppCode.SigControls.PDFViewer
|
||||
{
|
||||
public sealed class PdfClientAction
|
||||
{
|
||||
public string Action { get; set; }
|
||||
public string Message { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
||||
10
SIG.Web/AppCode/SigControls/PDFViewer/PdfDocumentComposer.cs
Normal file
10
SIG.Web/AppCode/SigControls/PDFViewer/PdfDocumentComposer.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace SIG.Web.AppCode.SigControls.PDFViewer
|
||||
{
|
||||
public sealed class PdfDocumentComposer
|
||||
{
|
||||
public byte[] Compose(byte[] originalPdf)
|
||||
{
|
||||
return originalPdf ?? new byte[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace SIG.Web.AppCode.SigControls.PDFViewer
|
||||
{
|
||||
public sealed class PdfSignatureAsset
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public byte[] Bytes { get; set; }
|
||||
public string ContentType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SIG.Web.AppCode.SigControls.PDFViewer
|
||||
{
|
||||
public sealed class PdfSignatureComposer
|
||||
{
|
||||
public byte[] ApplySignatures(byte[] originalPdf, IEnumerable<PdfSignaturePlacement> placements)
|
||||
{
|
||||
// Placeholder. Keep signature composition behind this class.
|
||||
return originalPdf ?? new byte[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace SIG.Web.AppCode.SigControls.PDFViewer
|
||||
{
|
||||
public sealed class PdfSignaturePlacement
|
||||
{
|
||||
public int Page { get; set; }
|
||||
public float X { get; set; }
|
||||
public float Y { get; set; }
|
||||
public float Width { get; set; }
|
||||
public float Height { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace SIG.Web.AppCode.SigControls.PDFViewer
|
||||
{
|
||||
public sealed class PdfSignatureSavingEventArgs : EventArgs
|
||||
{
|
||||
public PdfSignatureAsset Signature { get; }
|
||||
public bool Cancel { get; set; }
|
||||
|
||||
public PdfSignatureSavingEventArgs(PdfSignatureAsset signature)
|
||||
{
|
||||
Signature = signature;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace SIG.Web.AppCode.SigControls.PDFViewer
|
||||
{
|
||||
public sealed class PdfSignedDocumentEventArgs : EventArgs
|
||||
{
|
||||
public byte[] Bytes { get; }
|
||||
public string FileName { get; }
|
||||
|
||||
public PdfSignedDocumentEventArgs(byte[] bytes, string fileName)
|
||||
{
|
||||
Bytes = bytes;
|
||||
FileName = fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
SIG.Web/AppCode/SigControls/PDFViewer/PdfSourceMode.cs
Normal file
11
SIG.Web/AppCode/SigControls/PDFViewer/PdfSourceMode.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace SIG.Web.AppCode.SigControls.PDFViewer
|
||||
{
|
||||
public enum PdfSourceMode
|
||||
{
|
||||
None,
|
||||
Bytes,
|
||||
FilePath,
|
||||
Url,
|
||||
TempStorage
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace SIG.Web.AppCode.SigControls.PDFViewer
|
||||
{
|
||||
public sealed class PdfStoredSignature
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string DataUrl { get; set; }
|
||||
}
|
||||
}
|
||||
29
SIG.Web/AppCode/SigControls/PDFViewer/PdfTempStorage.cs
Normal file
29
SIG.Web/AppCode/SigControls/PDFViewer/PdfTempStorage.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Web;
|
||||
|
||||
namespace SIG.Web.AppCode.SigControls.PDFViewer
|
||||
{
|
||||
public static class PdfTempStorage
|
||||
{
|
||||
private const string Folder = "~/App_Data/PdfTemp";
|
||||
|
||||
public static string Save(byte[] bytes, string extension = ".pdf")
|
||||
{
|
||||
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
|
||||
var dir = HttpContext.Current.Server.MapPath(Folder);
|
||||
Directory.CreateDirectory(dir);
|
||||
var fileName = Guid.NewGuid().ToString("N") + extension;
|
||||
var path = Path.Combine(dir, fileName);
|
||||
File.WriteAllBytes(path, bytes);
|
||||
return path;
|
||||
}
|
||||
|
||||
public static byte[] Read(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
||||
return null;
|
||||
return File.ReadAllBytes(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user