From c4f958e688d18375baddc6d616c29555372c49e3 Mon Sep 17 00:00:00 2001 From: Jorge Luis caudillo esparza Date: Wed, 2 Sep 2026 01:08:02 -0700 Subject: [PATCH] -- --- AGENTS_ARQUITECTURA.md | 232 ++ AGENTS_MANTENIMIENTO_PROYECTO.md | 153 + AGENTS_WEBFORMS_ESTRUCTURA.md | 415 +++ Facturacion_Timbrado_Venta_20260828.sql | 12 +- ...tos_Ventas_Conceptos_Timbrado_20260827.sql | 123 +- ...os_Ventas_Flujo_Generar_Pagar_20260827.sql | 34 +- Fraccionamientos_Ventas_Tramites_20260827.sql | 210 +- Impuestos_Costos_Ventas_Tramites_20260901.sql | 2824 +++++++++++++++++ ...oPagoPendienteList_Actualizar_20260901.sql | 185 ++ .../Handlers/Facturacion/DescargaXmlCfdi.cs | 144 + .../Models/General/ModelosProductoServicio.cs | 18 + .../Models/Tramites/TramiteCatalogModels.cs | 13 +- .../Models/Ventas/ModelosVentaTramite.cs | 18 + .../General/RepositorioProductoServicio.cs | 16 + .../Tramites/TramiteCatalogRepository.cs | 12 + .../Ventas/RepositorioVentaTramite.cs | 28 + .../ConstructorXmlCfdi40Profact.cs | 25 +- .../FabricaBitacoraProductoServicio.cs | 10 + .../General/ServicioProductoServicio.cs | 14 + .../Tramites/TramiteCatalogBitacoraFactory.cs | 10 +- .../Tramites/TramiteCatalogService.cs | 9 +- .../Ventas/cfdi-venta-1019-documento-1.xml | 1 + .../Ventas/cfdi-venta-1020-documento-2.xml | 1 + .../Pages/Catalogos/ProductosServicios.aspx | 32 + .../Catalogos/ProductosServicios.aspx.cs | 30 + .../Pages/Catalogos/TipoTramiteDetalle.aspx | 117 +- .../Catalogos/TipoTramiteDetalle.aspx.cs | 160 +- SIG.Web/Pages/Ventas/VentaTramite.aspx.cs | 20 +- SIG.Web/Web.config | 2 + Ventas_Prueba_Limpieza_20260901.sql | 86 + 30 files changed, 4690 insertions(+), 264 deletions(-) create mode 100644 AGENTS_ARQUITECTURA.md create mode 100644 AGENTS_MANTENIMIENTO_PROYECTO.md create mode 100644 AGENTS_WEBFORMS_ESTRUCTURA.md create mode 100644 Impuestos_Costos_Ventas_Tramites_20260901.sql create mode 100644 PA_Vta_FraccionamientoPagoPendienteList_Actualizar_20260901.sql create mode 100644 SIG.Web/App_Code/Handlers/Facturacion/DescargaXmlCfdi.cs create mode 100644 SIG.Web/Documentos/Clientes/4/CFDI/Ventas/cfdi-venta-1019-documento-1.xml create mode 100644 SIG.Web/Documentos/Clientes/4/CFDI/Ventas/cfdi-venta-1020-documento-2.xml create mode 100644 Ventas_Prueba_Limpieza_20260901.sql diff --git a/AGENTS_ARQUITECTURA.md b/AGENTS_ARQUITECTURA.md new file mode 100644 index 0000000..76aca31 --- /dev/null +++ b/AGENTS_ARQUITECTURA.md @@ -0,0 +1,232 @@ +# AGENTS_ARQUITECTURA.md + +These rules complement `AGENTS.md` and are mandatory whenever the task changes project structure, C# architecture, database access, repositories, services, contracts, models, or application transactions. + +## Project folder rules + +Use this base structure: + +```text +SIG.Web/ + App_Code/ + 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: + +- The active application uses the ASP.NET Web Site runtime-compilation model. +- Shared C# source belongs in the exact special folder `App_Code`. +- Pages, master pages, controls, handlers, and `Global.asax` use `CodeFile` or source compiled from `App_Code`; do not introduce new `CodeBehind` dependencies on `SIG.Web.dll`. +- Keep third-party and external assemblies in `bin`, but do not build or deploy a project assembly named `SIG.Web.dll`. +- 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`. +- The active Web Site does not use explicit project compilation entries; C# files under `App_Code` compile automatically at runtime. + +--- + +## 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. + +--- + +## Repository ownership rules + +Repository ownership is determined by the data domain and stored-procedure prefix, not by the page or feature that currently consumes the data. + +- Procedures named `PA_Gral_*` belong behind contracts and repositories under `Contracts/General` and `Repositories/General`. +- General catalogs such as states, countries, municipalities, neighborhoods, and postal codes must not be placed in a security, registration, sales, or page-specific repository. +- A feature service such as public registration may orchestrate both its feature repository and `IGeneralCatalogRepository`, but it must not move general catalog access into the feature repository. +- Apply the same ownership rule to future procedure families: `PA_Sis_*` belongs to security/system infrastructure, `PA_Tra_*` to procedures, `PA_Vta_*` to sales, and so on. + +## Database executor ownership rules + +- `IDbExecutor`, `DbExecutor`, `StoredProcedureCommand`, `IDbCommand`, and direct stored-procedure execution may only be used inside classes under `Repositories` or repository database infrastructure. +- Services must not reference or receive `IDbExecutor`, create database commands, execute readers, or call stored procedures directly. +- A service that needs persisted data must depend on a repository contract. The concrete repository receives `IDbExecutor` and owns parameter configuration and result mapping. +- Keep the `DbExecutor` implementation under `Repositories/Database`; do not place it under `Services` or expose it publicly through `AppServices`. + +--- + +## 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. +- Services own application transactions when one business operation performs one or more repository mutations. +- Use `TransactionLogic` inside a `using` block around the repository calls and invoke `Commit()` only after every required repository result succeeds. +- Pages and repositories must not create, commit, roll back, or dispose application transactions. +- Repositories participate through the ambient `TransactionScope`; connection strings used for transactional work must not set `Enlist=false`. +- Select `TransactionScopeOption`, isolation level, and timeout in the service according to the business operation. Use `RequiresNew` or `Suppress` only when that behavior is intentional and documented by the call site. +- Do not include slow external operations such as SMTP or HTTP calls inside a database transaction. + +--- + +## 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. +- Access to modules and catalogs is controlled by the menu and page-access layer. Reusable catalog stored procedures must not query `Sis_Usuarios`, restrict execution by `CveRol`, or return access-denied messages for module authorization. +- Keep `@CveUsuarioEjecuta` in catalog procedures for the required signature and auditing when applicable, not to authorize access to the module. +- Do not invent or add authorization, role, user-client association, ownership, or business-prerequisite validations to any stored procedure unless the user explicitly requests that exact validation. The user prefers to add those validations manually. +- Mandatory: read and follow `AGENTS_STORED_PROCEDURES.md` for naming, output parameters, TRY/CATCH, logging, transactions, and physical 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. +- Mandatory: read and follow `AGENTS_CATALOG_STRUCTURE.md` for catalog titles, breadcrumbs, actions, filters, indicators, grids, quick-create modals, and detail pages. +- Mandatory: read and follow `AGENTS_DOCUMENT_STORAGE.md` for document folders, client access validation, protected file retrieval, and GIS file naming. +- Keep stored procedure names centralized. +- Repositories must use `CommandType.StoredProcedure` and parameterized inputs/outputs. +- Never concatenate raw user input into SQL. +- The database default collation and general text columns must use `Modern_Spanish_CS_AS`. +- Columns named `Codigo`, beginning with `Codigo`, or containing a real user login identifier such as `NombreUsuario`, `UsuarioPortal`, or `Acum_PortalUsuario` must use `Modern_Spanish_CI_AS`. +- `AgenteUsuario` is browser/device metadata, not a login identifier, and must use the general `Modern_Spanish_CS_AS` collation. +- Do not use `Modern_Spanish_CS_AI`. + +Recommended Web.config connection string pattern: + +```xml + + + + + +``` + +--- + +## 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. + +--- + diff --git a/AGENTS_MANTENIMIENTO_PROYECTO.md b/AGENTS_MANTENIMIENTO_PROYECTO.md new file mode 100644 index 0000000..775da70 --- /dev/null +++ b/AGENTS_MANTENIMIENTO_PROYECTO.md @@ -0,0 +1,153 @@ +# AGENTS_MANTENIMIENTO_PROYECTO.md + +These rules complement `AGENTS.md` and are mandatory whenever the task involves commands, security, error handling, user feedback, grids, PDF migration, file delivery, verification, or project evolution. + +## 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 +``` + +--- + +## 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 + +Controlled outcomes and technical failures must use different logs. + +- `@ErrorTipo = 1`, SQL exceptions, timeouts, contract failures, and unexpected exceptions go to the main technical log. +- `@ErrorTipo = 2` is a controlled warning. It must not be written to the main technical log. +- Controlled warnings go to daily compact files under `~/log/controlled`. +- Controlled log files retain only the current day and the previous two days. The retention is configured with `ControlledLogRetentionDays = 3`. +- Explicit controlled throws must use `ControlledApplicationException`; do not classify arbitrary `Exception` or `InvalidOperationException` instances as controlled. +- Controlled logs record date, source, procedure, URL, and friendly message, but omit full stack traces and replay SQL to remain compact. +- A controlled exception must still return a friendly result to the page and must never be presented as an unexpected application failure. + +- 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. + +## Action availability and feedback rules + +- Do not disable a button or primary action only because a business prerequisite is missing, such as an unassigned client or an empty related catalog. +- Keep the action available by default; when the user clicks it, validate the prerequisite and show a clear message explaining why the operation cannot continue and what must be done next. +- Never leave a disabled action without an immediately visible explanation. +- Before adding `Enabled="false"`, the HTML `disabled` attribute, or an equivalent disabled CSS/JavaScript state to a catalog or page action, ask the user whether disabling is acceptable or whether a message, modal, link, or other alternative should be used. +- A transient disabled state used only while a request is being processed to prevent duplicate submissions is allowed, but the action must be restored when processing finishes. + +--- + +## Grid display rules + +- Every new or modified `asp:GridView` must declare `GridLines="None"`. +- Its `CssClass` must include the exact utility class `Widthall` so the grid uses the full available width. +- Do not add `table-bordered` to a GridView. Existing visual classes such as `table`, `table-striped`, and `table-hover` may be retained when appropriate. +- Keep GridView declarations and their columns expanded and indented according to the source code readability rules. +- Detail-page navigation from a GridView or list must use `asp:HyperLink` or another real link with `NavigateUrl`; do not use `LinkButton`, `RowCommand`, or a postback only to open a detail page. +- URL-building variables and helpers must describe the row entity, for example `cveFraccionamiento`, `fraccionamientoDetalleUrl`, or `GetFraccionamientoDetalleUrl`. Avoid generic names such as `id`, `itemUrl`, or `detailUrl` when the entity is known. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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. + diff --git a/AGENTS_WEBFORMS_ESTRUCTURA.md b/AGENTS_WEBFORMS_ESTRUCTURA.md new file mode 100644 index 0000000..5b73ffc --- /dev/null +++ b/AGENTS_WEBFORMS_ESTRUCTURA.md @@ -0,0 +1,415 @@ +# AGENTS_WEBFORMS_ESTRUCTURA.md + +These rules complement `AGENTS.md` and are mandatory whenever the task changes WebForms pages, the master page, login, session behavior, bundles, assets, configuration, controls, or the application shell. + +## 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 ``, ``, ``, or `
`. +- Must use `` 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 ``. +- `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 ``. +- Prefer `<title><%: Page.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 + + + +``` + +Example in a normal page: + +```aspx + + + + + + + + +``` + +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. + +--- + +## WebForms tab and page-state rules + +- Register AjaxControlToolkit once in `Web.config` under `system.web/pages/controls` with the exact prefix `ajax`; do not add `<%@ Register ... %>` directives for it in individual pages. +- AjaxControlToolkit controls must use the `ajax:` prefix, for example `ajax:TabContainer` and `ajax:TabPanel`; do not use `ajaxToolkit:` or another page-specific prefix. +- Tabs in WebForms pages must use `ajax:TabContainer` and `ajax:TabPanel`; do not build page tabs with Bootstrap `nav-tabs`, custom hidden panels, or JavaScript-only tab switching. +- Use the `ScriptManager` owned by `Site.master` and the existing AjaxControlToolkit resources. Do not add another `ScriptManager` or duplicate Toolkit scripts/styles. +- Server-side page state such as current entity keys, selected child keys, editing mode, flags, and the active tab must be stored in typed `ViewState` properties when it must survive postback. +- Do not add `asp:HiddenField` merely to preserve server-side state across postbacks. A hidden field is allowed only when client-side JavaScript must read or change the value. +- Hidden-field values are client-controlled and must never be trusted for authorization or sensitive decisions; validate them again on the server when their use is unavoidable. +- Keep ViewState values small and serializable. Do not store database connections, commands, readers, services, repositories, or unnecessarily large result sets in ViewState. +- ViewState property and key names must identify the entity and purpose, for example `CveFraccionamientoActual` and `CveFraccionamientoEtapaActual`. +- When a postback needs to recover related values from an already loaded catalog, store the typed serializable object list in a typed `ViewState` property and locate the selected object by its key. +- Do not copy server-owned catalog or model values into HTML `data-*` attributes, CSS classes, hidden fields, or other client-controlled markup merely to recover them during a postback. Read them from the typed object list stored in `ViewState`. + +--- + +## 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. + +--- + +## 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 + +``` + +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 + +``` + +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 ``: + +```aspx +<%: Scripts.Render("~/bundles/modernizr") %> +<%: Styles.Render("~/Content/AjaxControlToolkit/Styles/Bundle") %> +<%: Styles.Render("~/Metronic/assets/global/css/cssV1") %> + +``` + +Inside `` near the top: + +```aspx + +<%: Scripts.Render("~/bundles/scripts") %> +``` + +Before closing ``: + +```aspx +<%: Scripts.Render("~/Metronic/assets/global/scripts/JSV1") %> +<%: Scripts.Render("~/bundles/highcharts") %> + +``` + +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. +- Module visibility and page access are separate concerns: `Sis_Modulos.Visible` controls whether an item is shown in the menu, but it must not deny direct page access. +- `PA_Sis_PaginaAccessCheck` must validate role assignment, `PuedeVer`, active state, and URL. It must not require `Sis_Modulos.Visible = 1`. + +--- + +## 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 +``` + +--- + +## 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. + +--- + +## Home page rules + +Use only one active home page convention. + +For local development, the canonical home URL is: + +```text +http://localhost:52706/Inicio.aspx +``` + +The application root must be equivalent and redirect explicitly to that page: + +```text +http://localhost:52706/ -> http://localhost:52706/Inicio.aspx +``` + +Keep the root redirect and the `Inicio.aspx` default document consistent. Do not introduce another startup page. + +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`. + +--- + diff --git a/Facturacion_Timbrado_Venta_20260828.sql b/Facturacion_Timbrado_Venta_20260828.sql index 9364de3..8f9bbb1 100644 --- a/Facturacion_Timbrado_Venta_20260828.sql +++ b/Facturacion_Timbrado_Venta_20260828.sql @@ -175,17 +175,17 @@ BEGIN UM.UnidadMedida AS Unidad, PS.PS AS Descripcion, VD.PrecioUnitario AS ValorUnitario, + VD.PrecioUnitario AS PU, + VD.Importe AS Total, VD.Descuento, OI.CodigoObjetoImpuesto AS ObjetoImpuesto, CONVERT( decimal(19, 6), - ROUND(VD.Cantidad * VD.PrecioUnitario, 6) - VD.Descuento + VD.Importe - VD.Descuento ) AS BaseIVA, - CONVERT( - decimal(19, 6), - ISNULL(PS.IVA, 0) / 100.0 - ) AS TasaIVA, - CONVERT(decimal(19, 6), VD.IVA) AS ImporteIVA + CONVERT(decimal(19, 6), 0.16) AS TasaIVA, + CONVERT(decimal(19, 6), VD.IVA) AS ImporteIVA, + CONVERT(decimal(19, 6), 0.16) AS PorcentajeIVA FROM dbo.VentasDetalle AS VD INNER JOIN dbo.ProductosServicios AS PS ON PS.CvePS = VD.CvePS diff --git a/Fraccionamientos_Ventas_Conceptos_Timbrado_20260827.sql b/Fraccionamientos_Ventas_Conceptos_Timbrado_20260827.sql index 8d1b679..eef85c3 100644 --- a/Fraccionamientos_Ventas_Conceptos_Timbrado_20260827.sql +++ b/Fraccionamientos_Ventas_Conceptos_Timbrado_20260827.sql @@ -579,10 +579,9 @@ BEGIN DECLARE @Total decimal(18, 2); SELECT - @SubTotal = CONVERT(decimal(18, 2), SUM(ROUND(Cantidad * PrecioUnitario, 2))), + @SubTotal = CONVERT(decimal(18, 2), SUM(Importe)), @Descuento = CONVERT(decimal(18, 2), SUM(Descuento)), - @IVA = CONVERT(decimal(18, 2), SUM(IVA)), - @Total = CONVERT(decimal(18, 2), SUM(Importe)) + @IVA = CONVERT(decimal(18, 2), SUM(IVA)) FROM dbo.VentasDetalle WHERE CveVenta = @CveVenta; @@ -592,11 +591,17 @@ BEGIN RETURN; END; + SET @Descuento = ISNULL(@Descuento, 0); + SET @IVA = ISNULL(@IVA, 0); + SET @Total = CONVERT( + decimal(18, 2), + ROUND(@SubTotal - @Descuento + @IVA, 2)); + UPDATE dbo.Ventas SET SubTotal = @SubTotal, - Descuento = ISNULL(@Descuento, 0), - IVA = ISNULL(@IVA, 0), + Descuento = @Descuento, + IVA = @IVA, Total = @Total, CveUsuarioModifico = @CveUsuarioEjecuta WHERE CveVenta = @CveVenta; @@ -738,20 +743,6 @@ BEGIN RETURN; END; - IF NOT EXISTS - ( - SELECT 1 - FROM dbo.Vta_Pagos - WHERE CveVenta = @CveVenta - AND CveSolicitud = @CveSolicitud - AND CveTramitePago = @CveTramitePago - AND EsActivo = 1 - ) - BEGIN - SELECT @ErrorMostrar = 'La venta debe estar pagada antes de timbrarla.', @ErrorTipo = 2; - RETURN; - END; - IF EXISTS ( SELECT 1 @@ -817,11 +808,17 @@ BEGIN UM.UnidadMedida AS Unidad, PS.PS AS Descripcion, VD.PrecioUnitario AS ValorUnitario, + VD.PrecioUnitario AS PU, + VD.Importe AS Total, VD.Descuento, OI.CodigoObjetoImpuesto AS ObjetoImpuesto, - CONVERT(decimal(19, 6), ROUND(VD.Cantidad * VD.PrecioUnitario, 6) - VD.Descuento) AS BaseIVA, - CONVERT(decimal(19, 6), ISNULL(PS.IVA, 0) / 100.0) AS TasaIVA, - CONVERT(decimal(19, 6), VD.IVA) AS ImporteIVA + CONVERT( + decimal(19, 6), + VD.Importe - VD.Descuento + ) AS BaseIVA, + CONVERT(decimal(19, 6), 0.16) AS TasaIVA, + CONVERT(decimal(19, 6), VD.IVA) AS ImporteIVA, + CONVERT(decimal(19, 6), 0.16) AS PorcentajeIVA FROM dbo.VentasDetalle AS VD INNER JOIN dbo.ProductosServicios AS PS ON PS.CvePS = VD.CvePS @@ -860,6 +857,7 @@ BEGIN BEGIN TRY DECLARE @CveVenta int; + DECLARE @PorcentajeIVA decimal(9, 6) = 0.16; SELECT @CveVenta = SP.CveVenta FROM dbo.Tra_SolicitudesPagos AS SP @@ -935,11 +933,17 @@ BEGIN ELSE PS.Codigo COLLATE DATABASE_DEFAULT + ' - ' + PS.PS COLLATE DATABASE_DEFAULT END AS ProductoServicio, - CONVERT(decimal(18, 4), D.Cantidad) AS Cantidad, - CONVERT(decimal(18, 2), PS.Precio) AS PrecioUnitario, + CONVERT(decimal(18, 4), 1) AS Cantidad, + CONVERT(decimal(18, 2), Costo.Importe) AS PrecioUnitario, CONVERT(decimal(18, 2), 0) AS Descuento, - CONVERT(decimal(18, 2), ROUND(D.Cantidad * PS.Precio * ISNULL(PS.IVA, 0) / 100, 2)) AS IVA, - CONVERT(decimal(18, 2), ROUND(D.Cantidad * PS.Precio, 2) + ROUND(D.Cantidad * PS.Precio * ISNULL(PS.IVA, 0) / 100, 2)) AS Importe + CONVERT( + decimal(18, 2), + ROUND(Costo.Importe * @PorcentajeIVA, 2) + ) AS IVA, + CONVERT( + decimal(18, 2), + ROUND(Costo.Importe, 2) + ) AS Importe FROM dbo.Tra_Solicitudes AS S INNER JOIN dbo.Tra_Pagos AS P ON P.CveTramitePago = @CveTramitePago @@ -949,6 +953,30 @@ BEGIN ON D.CveTramitePago = P.CveTramitePago INNER JOIN dbo.ProductosServicios AS PS ON PS.CvePS = D.CvePS + CROSS APPLY + ( + SELECT TOP (1) + CT.Importe + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = S.CveTipoTramite + AND CT.CvePS = D.CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, GETDATE()) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, GETDATE()) + ) + ORDER BY + ISNULL( + CT.FechaInicioVigencia, + CONVERT(date, '19000101')) DESC, + CT.CveCostoTramite DESC + ) AS Costo INNER JOIN dbo.UnidadesMedida AS UM ON UM.CveUnidadMedida = D.CveUM INNER JOIN dbo.Clasificaciones AS C @@ -992,7 +1020,8 @@ BEGIN BEGIN TRY DECLARE @CveVenta int; - DECLARE @PorcentajeIVA decimal(18, 6); + DECLARE @PorcentajeIVA decimal(9, 6) = 0.16; + DECLARE @Descuento decimal(18, 2) = 0; DECLARE @IVA decimal(18, 2); DECLARE @Importe decimal(18, 2); @@ -1021,19 +1050,43 @@ BEGIN RETURN; END; - SELECT @PorcentajeIVA = CONVERT(decimal(18, 6), ISNULL(IVA, 0)) - FROM dbo.ProductosServicios - WHERE CvePS = @CvePS - AND Estado = 1; - - IF @PorcentajeIVA IS NULL OR @Cantidad <= 0 OR @PrecioUnitario < 0 + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.ProductosServicios + WHERE CvePS = @CvePS + AND Estado = 1 + ) + OR @Cantidad <= 0 + OR @PrecioUnitario < 0 BEGIN SELECT @ErrorMostrar = 'Capture un producto activo, cantidad y precio unitario válidos.', @ErrorTipo = 2; RETURN; END; - SET @IVA = CONVERT(decimal(18, 2), ROUND(@Cantidad * @PrecioUnitario * @PorcentajeIVA / 100, 2)); - SET @Importe = CONVERT(decimal(18, 2), ROUND(@Cantidad * @PrecioUnitario, 2) + @IVA); + IF @CveVentaDetalle IS NOT NULL + BEGIN + SELECT @Descuento = Descuento + FROM dbo.VentasDetalle + WHERE CveVentaDetalle = @CveVentaDetalle + AND CveVenta = @CveVenta; + END; + + SET @Importe = CONVERT( + decimal(18, 2), + ROUND(@Cantidad * @PrecioUnitario, 2)); + + IF @Descuento > @Importe + BEGIN + SELECT + @ErrorMostrar = 'El descuento del concepto no puede ser mayor que su importe.', + @ErrorTipo = 2; + RETURN; + END; + + SET @IVA = CONVERT( + decimal(18, 2), + ROUND((@Importe - @Descuento) * @PorcentajeIVA, 2)); IF @CveVentaDetalle IS NULL BEGIN diff --git a/Fraccionamientos_Ventas_Flujo_Generar_Pagar_20260827.sql b/Fraccionamientos_Ventas_Flujo_Generar_Pagar_20260827.sql index 62413b1..8a9aa9c 100644 --- a/Fraccionamientos_Ventas_Flujo_Generar_Pagar_20260827.sql +++ b/Fraccionamientos_Ventas_Flujo_Generar_Pagar_20260827.sql @@ -104,6 +104,8 @@ BEGIN @Return = 0; BEGIN TRY + DECLARE @PorcentajeIVA decimal(9, 6) = 0.16; + IF NOT EXISTS ( SELECT 1 @@ -125,16 +127,14 @@ BEGIN COUNT_BIG(*) AS NumeroConceptos, SUM( ROUND( - CONVERT(decimal(18, 4), D.Cantidad) * - CONVERT(decimal(18, 4), PS.Precio), + CONVERT(decimal(18, 4), Costo.Importe), 2 ) ) AS SubTotal, SUM( ROUND( - CONVERT(decimal(18, 4), D.Cantidad) * - CONVERT(decimal(18, 4), PS.Precio) * - CONVERT(decimal(18, 6), ISNULL(PS.IVA, 0)) / 100, + CONVERT(decimal(18, 4), Costo.Importe) * + @PorcentajeIVA, 2 ) ) AS IVA @@ -146,6 +146,30 @@ BEGIN ON D.CveTramitePago = P.CveTramitePago INNER JOIN dbo.ProductosServicios AS PS ON PS.CvePS = D.CvePS + OUTER APPLY + ( + SELECT TOP (1) + CT.Importe + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = S.CveTipoTramite + AND CT.CvePS = D.CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, GETDATE()) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, GETDATE()) + ) + ORDER BY + ISNULL( + CT.FechaInicioVigencia, + CONVERT(date, '19000101')) DESC, + CT.CveCostoTramite DESC + ) AS Costo WHERE S.CveFraccionamiento = @CveFraccionamiento GROUP BY S.CveSolicitud, diff --git a/Fraccionamientos_Ventas_Tramites_20260827.sql b/Fraccionamientos_Ventas_Tramites_20260827.sql index e773e19..c47c62e 100644 --- a/Fraccionamientos_Ventas_Tramites_20260827.sql +++ b/Fraccionamientos_Ventas_Tramites_20260827.sql @@ -214,6 +214,8 @@ BEGIN @Return = 0; BEGIN TRY + DECLARE @PorcentajeIVA decimal(9, 6) = 0.16; + IF NOT EXISTS ( SELECT 1 @@ -235,16 +237,14 @@ BEGIN COUNT_BIG(*) AS NumeroConceptos, SUM( ROUND( - CONVERT(decimal(18, 4), D.Cantidad) * - CONVERT(decimal(18, 4), PS.Precio), + CONVERT(decimal(18, 4), Costo.Importe), 2 ) ) AS SubTotal, SUM( ROUND( - CONVERT(decimal(18, 4), D.Cantidad) * - CONVERT(decimal(18, 4), PS.Precio) * - CONVERT(decimal(18, 6), ISNULL(PS.IVA, 0)) / 100, + CONVERT(decimal(18, 4), Costo.Importe) * + @PorcentajeIVA, 2 ) ) AS IVA @@ -256,6 +256,30 @@ BEGIN ON D.CveTramitePago = P.CveTramitePago INNER JOIN dbo.ProductosServicios AS PS ON PS.CvePS = D.CvePS + OUTER APPLY + ( + SELECT TOP (1) + CT.Importe + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = S.CveTipoTramite + AND CT.CvePS = D.CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, GETDATE()) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, GETDATE()) + ) + ORDER BY + ISNULL( + CT.FechaInicioVigencia, + CONVERT(date, '19000101')) DESC, + CT.CveCostoTramite DESC + ) AS Costo WHERE S.CveFraccionamiento = @CveFraccionamiento AND NOT EXISTS ( @@ -555,6 +579,8 @@ BEGIN @Return = 0; BEGIN TRY + DECLARE @PorcentajeIVA decimal(9, 6) = 0.16; + SELECT D.CveTramitePagoDetalle, D.CvePS, @@ -585,29 +611,21 @@ BEGIN ' - ' + PS.PS COLLATE DATABASE_DEFAULT END AS ProductoServicio, - CONVERT(decimal(18, 4), D.Cantidad) AS Cantidad, - CONVERT(decimal(18, 2), PS.Precio) AS PrecioUnitario, + CONVERT(decimal(18, 4), 1) AS Cantidad, + CONVERT(decimal(18, 2), Costo.Importe) AS PrecioUnitario, CONVERT(decimal(18, 2), 0) AS Descuento, CONVERT( decimal(18, 2), ROUND( - CONVERT(decimal(18, 4), D.Cantidad) * - CONVERT(decimal(18, 4), PS.Precio) * - CONVERT(decimal(18, 6), ISNULL(PS.IVA, 0)) / 100, + CONVERT(decimal(18, 4), Costo.Importe) * + @PorcentajeIVA, 2 ) ) AS IVA, CONVERT( decimal(18, 2), ROUND( - CONVERT(decimal(18, 4), D.Cantidad) * - CONVERT(decimal(18, 4), PS.Precio), - 2 - ) + - ROUND( - CONVERT(decimal(18, 4), D.Cantidad) * - CONVERT(decimal(18, 4), PS.Precio) * - CONVERT(decimal(18, 6), ISNULL(PS.IVA, 0)) / 100, + CONVERT(decimal(18, 4), Costo.Importe), 2 ) ) AS Importe @@ -620,6 +638,33 @@ BEGIN ON D.CveTramitePago = P.CveTramitePago INNER JOIN dbo.ProductosServicios AS PS ON PS.CvePS = D.CvePS + CROSS APPLY + ( + SELECT TOP (1) + CT.Importe + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = + ( + SELECT S.CveTipoTramite + FROM dbo.Tra_Solicitudes AS S + WHERE S.CveSolicitud = @CveSolicitud + ) + AND CT.CvePS = D.CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, GETDATE()) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, GETDATE()) + ) + ORDER BY + ISNULL(CT.FechaInicioVigencia, CONVERT(date, '19000101')) DESC, + CT.CveCostoTramite DESC + ) AS Costo INNER JOIN dbo.UnidadesMedida AS UM ON UM.CveUnidadMedida = D.CveUM INNER JOIN dbo.Clasificaciones AS C @@ -669,12 +714,15 @@ BEGIN BEGIN TRY DECLARE + @PorcentajeIVA decimal(9, 6) = 0.16, + @CveVendedor int = 1, @CveCliente int, @CveSubProyecto int, @CveEstatusFactura int, @CveVenta int, @Folio int, @SubTotal decimal(18, 2), + @Descuento decimal(18, 2) = 0, @IVA decimal(18, 2), @Total decimal(18, 2), @Fecha datetime = GETDATE(); @@ -698,6 +746,43 @@ BEGIN RETURN; END; + IF EXISTS + ( + SELECT 1 + FROM dbo.Tra_Solicitudes AS S + INNER JOIN dbo.Tra_Pagos AS P + ON P.CveTramitePago = @CveTramitePago + AND P.CveTipoTramite = S.CveTipoTramite + AND P.EsActivo = 1 + INNER JOIN dbo.Tra_PagosDetalle AS D + ON D.CveTramitePago = P.CveTramitePago + WHERE S.CveSolicitud = @CveSolicitud + AND NOT EXISTS + ( + SELECT 1 + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = S.CveTipoTramite + AND CT.CvePS = D.CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, @Fecha) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, @Fecha) + ) + ) + ) + BEGIN + SELECT + @ErrorMostrar = 'Uno o más conceptos no tienen un costo vigente configurado.', + @ErrorTipo = 2; + RETURN; + END; + IF EXISTS ( SELECT 1 @@ -740,8 +825,7 @@ BEGIN decimal(18, 2), SUM( ROUND( - CONVERT(decimal(18, 4), D.Cantidad) * - CONVERT(decimal(18, 4), PS.Precio), + CONVERT(decimal(18, 4), Costo.Importe), 2 ) ) @@ -750,9 +834,8 @@ BEGIN decimal(18, 2), SUM( ROUND( - CONVERT(decimal(18, 4), D.Cantidad) * - CONVERT(decimal(18, 4), PS.Precio) * - CONVERT(decimal(18, 6), ISNULL(PS.IVA, 0)) / 100, + CONVERT(decimal(18, 4), Costo.Importe) * + @PorcentajeIVA, 2 ) ) @@ -760,6 +843,33 @@ BEGIN FROM dbo.Tra_PagosDetalle AS D INNER JOIN dbo.ProductosServicios AS PS ON PS.CvePS = D.CvePS + CROSS APPLY + ( + SELECT TOP (1) + CT.Importe + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = + ( + SELECT S.CveTipoTramite + FROM dbo.Tra_Solicitudes AS S + WHERE S.CveSolicitud = @CveSolicitud + ) + AND CT.CvePS = D.CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, @Fecha) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, @Fecha) + ) + ORDER BY + ISNULL(CT.FechaInicioVigencia, CONVERT(date, '19000101')) DESC, + CT.CveCostoTramite DESC + ) AS Costo WHERE D.CveTramitePago = @CveTramitePago; IF @SubTotal IS NULL @@ -771,7 +881,7 @@ BEGIN END; SET @IVA = ISNULL(@IVA, 0); - SET @Total = @SubTotal + @IVA; + SET @Total = @SubTotal - @Descuento + @IVA; SELECT @Folio = ISNULL(MAX(Folio), 0) + 1 @@ -787,10 +897,12 @@ BEGIN Folio, Fecha, CveCliente, + CveVendedor, CveMetodoPago, CondicionesPago, FormaPago, SubTotal, + Descuento, IVA, Total, Observaciones, @@ -813,10 +925,12 @@ BEGIN @Folio, @Fecha, @CveCliente, + @CveVendedor, 2, 0, 99, @SubTotal, + @Descuento, @IVA, @Total, ISNULL(@Observaciones, ''), @@ -854,30 +968,22 @@ BEGIN @CveVenta, D.CvePS, @CveSolicitud, - NULL, - D.Cantidad, - CONVERT(decimal(18, 2), PS.Precio), + Costo.CveCostoTramite, + CONVERT(decimal(18, 4), 1), + CONVERT(decimal(18, 2), Costo.Importe), CONVERT(decimal(18, 2), 0), CONVERT( decimal(18, 2), ROUND( - CONVERT(decimal(18, 4), D.Cantidad) * - CONVERT(decimal(18, 4), PS.Precio) * - CONVERT(decimal(18, 6), ISNULL(PS.IVA, 0)) / 100, + CONVERT(decimal(18, 4), Costo.Importe) * + @PorcentajeIVA, 2 ) ), CONVERT( decimal(18, 2), ROUND( - CONVERT(decimal(18, 4), D.Cantidad) * - CONVERT(decimal(18, 4), PS.Precio), - 2 - ) + - ROUND( - CONVERT(decimal(18, 4), D.Cantidad) * - CONVERT(decimal(18, 4), PS.Precio) * - CONVERT(decimal(18, 6), ISNULL(PS.IVA, 0)) / 100, + CONVERT(decimal(18, 4), Costo.Importe), 2 ) ), @@ -887,6 +993,34 @@ BEGIN FROM dbo.Tra_PagosDetalle AS D INNER JOIN dbo.ProductosServicios AS PS ON PS.CvePS = D.CvePS + CROSS APPLY + ( + SELECT TOP (1) + CT.CveCostoTramite, + CT.Importe + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = + ( + SELECT S.CveTipoTramite + FROM dbo.Tra_Solicitudes AS S + WHERE S.CveSolicitud = @CveSolicitud + ) + AND CT.CvePS = D.CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, @Fecha) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, @Fecha) + ) + ORDER BY + ISNULL(CT.FechaInicioVigencia, CONVERT(date, '19000101')) DESC, + CT.CveCostoTramite DESC + ) AS Costo WHERE D.CveTramitePago = @CveTramitePago; INSERT INTO dbo.Tra_SolicitudesPagos diff --git a/Impuestos_Costos_Ventas_Tramites_20260901.sql b/Impuestos_Costos_Ventas_Tramites_20260901.sql new file mode 100644 index 0000000..b7de799 --- /dev/null +++ b/Impuestos_Costos_Ventas_Tramites_20260901.sql @@ -0,0 +1,2824 @@ +SET NOCOUNT ON; +GO + +UPDATE dbo.ProductosServicios +SET CveObjetoImpuesto = 2, + IVA = NULL +WHERE CvePS = 9; +GO + +IF COL_LENGTH(N'dbo.Tra_CostosTramite', N'CveObjetoImpuesto') IS NULL +BEGIN + EXEC sys.sp_executesql N' + ALTER TABLE dbo.Tra_CostosTramite + ADD CveObjetoImpuesto tinyint NULL;'; +END; +GO + +UPDATE CT +SET CveObjetoImpuesto = ISNULL(PS.CveObjetoImpuesto, 1) +FROM dbo.Tra_CostosTramite AS CT +INNER JOIN dbo.ProductosServicios AS PS + ON PS.CvePS = CT.CvePS +WHERE CT.CveObjetoImpuesto IS NULL; +GO + +IF EXISTS +( + SELECT 1 + FROM sys.columns + WHERE object_id = OBJECT_ID(N'dbo.Tra_CostosTramite') + AND name = N'CveObjetoImpuesto' + AND is_nullable = 1 +) +BEGIN + UPDATE dbo.Tra_CostosTramite + SET CveObjetoImpuesto = 1 + WHERE CveObjetoImpuesto IS NULL; + + ALTER TABLE dbo.Tra_CostosTramite + ALTER COLUMN CveObjetoImpuesto tinyint NOT NULL; +END; +GO + +IF NOT EXISTS +( + SELECT 1 + FROM sys.default_constraints + WHERE parent_object_id = OBJECT_ID(N'dbo.Tra_CostosTramite') + AND name = N'DF_Tra_CostosTramite_CveObjetoImpuesto' +) +BEGIN + ALTER TABLE dbo.Tra_CostosTramite + ADD CONSTRAINT DF_Tra_CostosTramite_CveObjetoImpuesto + DEFAULT (1) FOR CveObjetoImpuesto; +END; +GO + +IF OBJECT_ID(N'dbo.Tra_CostosTramiteImpuestos', N'U') IS NULL +BEGIN + CREATE TABLE dbo.Tra_CostosTramiteImpuestos + ( + CveCostoTramiteImpuesto int IDENTITY(1, 1) NOT NULL, + CveCostoTramite int NOT NULL, + EsRetencion bit CONSTRAINT DF_Tra_CostosTramiteImpuestos_EsRetencion + DEFAULT (0) NOT NULL, + Impuesto varchar(3) COLLATE Modern_Spanish_CS_AS NOT NULL, + TipoFactor varchar(6) COLLATE Modern_Spanish_CS_AS NOT NULL, + TasaOCuota decimal(19, 6) NULL, + FechaAlta datetime CONSTRAINT DF_Tra_CostosTramiteImpuestos_FechaAlta + DEFAULT (GETDATE()) NOT NULL, + CveUsuario int NOT NULL, + CONSTRAINT PK_Tra_CostosTramiteImpuestos + PRIMARY KEY CLUSTERED (CveCostoTramiteImpuesto), + CONSTRAINT FK_Tra_CostosTramiteImpuestos_Costo + FOREIGN KEY (CveCostoTramite) + REFERENCES dbo.Tra_CostosTramite (CveCostoTramite), + CONSTRAINT FK_Tra_CostosTramiteImpuestos_Usuario + FOREIGN KEY (CveUsuario) + REFERENCES dbo.Sis_Usuarios (CveUsuario), + CONSTRAINT CK_Tra_CostosTramiteImpuestos_TipoFactor + CHECK (TipoFactor IN ('Tasa', 'Cuota', 'Exento')), + CONSTRAINT CK_Tra_CostosTramiteImpuestos_Tasa + CHECK + ( + (TipoFactor = 'Exento' AND TasaOCuota IS NULL) + OR + (TipoFactor <> 'Exento' AND TasaOCuota >= 0) + ) + ); + + CREATE UNIQUE NONCLUSTERED INDEX + UX_Tra_CostosTramiteImpuestos_Configuracion + ON dbo.Tra_CostosTramiteImpuestos + ( + CveCostoTramite, + EsRetencion, + Impuesto, + TipoFactor, + TasaOCuota + ); +END; +GO + +IF COL_LENGTH(N'dbo.VentasDetalle', N'CveObjetoImpuesto') IS NULL +BEGIN + EXEC sys.sp_executesql N' + ALTER TABLE dbo.VentasDetalle + ADD CveObjetoImpuesto tinyint NULL;'; +END; +GO + +UPDATE VD +SET CveObjetoImpuesto = COALESCE( + CT.CveObjetoImpuesto, + PS.CveObjetoImpuesto, + 1) +FROM dbo.VentasDetalle AS VD +INNER JOIN dbo.ProductosServicios AS PS + ON PS.CvePS = VD.CvePS +LEFT JOIN dbo.Tra_CostosTramite AS CT + ON CT.CveCostoTramite = VD.CveCostoTramite +WHERE VD.CveObjetoImpuesto IS NULL; +GO + +IF EXISTS +( + SELECT 1 + FROM sys.columns + WHERE object_id = OBJECT_ID(N'dbo.VentasDetalle') + AND name = N'CveObjetoImpuesto' + AND is_nullable = 1 +) +BEGIN + UPDATE dbo.VentasDetalle + SET CveObjetoImpuesto = 1 + WHERE CveObjetoImpuesto IS NULL; + + ALTER TABLE dbo.VentasDetalle + ALTER COLUMN CveObjetoImpuesto tinyint NOT NULL; +END; +GO + +IF NOT EXISTS +( + SELECT 1 + FROM sys.default_constraints + WHERE parent_object_id = OBJECT_ID(N'dbo.VentasDetalle') + AND name = N'DF_VentasDetalle_CveObjetoImpuesto' +) +BEGIN + ALTER TABLE dbo.VentasDetalle + ADD CONSTRAINT DF_VentasDetalle_CveObjetoImpuesto + DEFAULT (1) FOR CveObjetoImpuesto; +END; +GO + +IF OBJECT_ID(N'dbo.VentasDetalleImpuestos', N'U') IS NULL +BEGIN + CREATE TABLE dbo.VentasDetalleImpuestos + ( + CveVentaDetalleImpuesto int IDENTITY(1, 1) NOT NULL, + CveVentaDetalle int NOT NULL, + EsRetencion bit CONSTRAINT DF_VentasDetalleImpuestos_EsRetencion + DEFAULT (0) NOT NULL, + Base decimal(19, 6) NOT NULL, + Impuesto varchar(3) COLLATE Modern_Spanish_CS_AS NOT NULL, + TipoFactor varchar(6) COLLATE Modern_Spanish_CS_AS NOT NULL, + TasaOCuota decimal(19, 6) NULL, + Importe decimal(18, 2) CONSTRAINT DF_VentasDetalleImpuestos_Importe + DEFAULT (0) NOT NULL, + FechaAlta datetime CONSTRAINT DF_VentasDetalleImpuestos_FechaAlta + DEFAULT (GETDATE()) NOT NULL, + CveUsuario int NOT NULL, + CONSTRAINT PK_VentasDetalleImpuestos + PRIMARY KEY CLUSTERED (CveVentaDetalleImpuesto), + CONSTRAINT FK_VentasDetalleImpuestos_VentaDetalle + FOREIGN KEY (CveVentaDetalle) + REFERENCES dbo.VentasDetalle (CveVentaDetalle), + CONSTRAINT FK_VentasDetalleImpuestos_Usuario + FOREIGN KEY (CveUsuario) + REFERENCES dbo.Sis_Usuarios (CveUsuario), + CONSTRAINT CK_VentasDetalleImpuestos_Importes + CHECK (Base >= 0 AND Importe >= 0), + CONSTRAINT CK_VentasDetalleImpuestos_TipoFactor + CHECK (TipoFactor IN ('Tasa', 'Cuota', 'Exento')), + CONSTRAINT CK_VentasDetalleImpuestos_Tasa + CHECK + ( + (TipoFactor = 'Exento' AND TasaOCuota IS NULL) + OR + (TipoFactor <> 'Exento' AND TasaOCuota >= 0) + ) + ); + + CREATE UNIQUE NONCLUSTERED INDEX + UX_VentasDetalleImpuestos_Configuracion + ON dbo.VentasDetalleImpuestos + ( + CveVentaDetalle, + EsRetencion, + Impuesto, + TipoFactor, + TasaOCuota + ); +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Tra_ProductoServicioList + @Texto varchar(200) = NULL, + @IncluirInactivos bit = 1, + @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 + SET @Texto = NULLIF(LTRIM(RTRIM(@Texto)), ''); + + SELECT + PS.CvePS, + PS.CveUnidadMedida, + UM.UnidadMedida, + UM.Abreviatura, + PS.Codigo, + PS.PS, + CONVERT(decimal(18, 2), PS.Precio) AS Precio, + PS.CveObjetoImpuesto, + OI.CodigoObjetoImpuesto, + CONVERT( + decimal(9, 6), + CASE + WHEN PS.CveObjetoImpuesto = 1 THEN 0 + WHEN PS.IVA IS NULL OR PS.IVA = -1 THEN 0.16 + ELSE PS.IVA + END) AS PorcentajeIVA, + PS.Estado AS EsActivo + FROM dbo.ProductosServicios AS PS + LEFT JOIN dbo.UnidadesMedida AS UM + ON UM.CveUnidadMedida = PS.CveUnidadMedida + INNER JOIN dbo.Gral_SAT_ObjetoImpuesto AS OI + ON OI.CveObjetoImpuesto = PS.CveObjetoImpuesto + WHERE (@IncluirInactivos = 1 OR PS.Estado = 1) + AND + ( + @Texto IS NULL + OR PS.Codigo LIKE '%' + @Texto + '%' + OR PS.PS LIKE '%' + @Texto + '%' + ) + ORDER BY PS.PS, PS.Codigo; + + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al consultar productos y servicios; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Tra_ProductoServicioList'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Tra_ProductoServicioGet + @CvePS 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 + PS.CvePS, + PS.CveUnidadMedida, + UM.UnidadMedida, + UM.Abreviatura, + PS.Codigo, + PS.PS, + CONVERT(decimal(18, 2), PS.Precio) AS Precio, + PS.CveObjetoImpuesto, + OI.CodigoObjetoImpuesto, + CONVERT( + decimal(9, 6), + CASE + WHEN PS.CveObjetoImpuesto = 1 THEN 0 + WHEN PS.IVA IS NULL OR PS.IVA = -1 THEN 0.16 + ELSE PS.IVA + END) AS PorcentajeIVA, + PS.Estado AS EsActivo + FROM dbo.ProductosServicios AS PS + LEFT JOIN dbo.UnidadesMedida AS UM + ON UM.CveUnidadMedida = PS.CveUnidadMedida + INNER JOIN dbo.Gral_SAT_ObjetoImpuesto AS OI + ON OI.CveObjetoImpuesto = PS.CveObjetoImpuesto + WHERE PS.CvePS = @CvePS; + + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al consultar el producto o servicio; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Tra_ProductoServicioGet'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Tra_PagoDetalleList + @CveTramitePago 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 + D.CveTramitePagoDetalle, + D.CveTramitePago, + P.Nombre AS Pago, + D.CvePS, + PS.Codigo, + PS.PS, + D.CveUM, + UM.UnidadMedida, + UM.Abreviatura, + D.Cantidad, + CONVERT(decimal(18, 2), PS.Precio) AS PrecioProducto, + CONVERT(decimal(18, 2), COALESCE(Costo.Importe, PS.Precio)) AS Importe, + COALESCE(Costo.CveObjetoImpuesto, PS.CveObjetoImpuesto) AS CveObjetoImpuesto, + OI.CodigoObjetoImpuesto, + OI.CodigoObjetoImpuesto COLLATE Modern_Spanish_CS_AS + ' - ' + OI.ObjetoImpuesto AS ObjetoImpuesto, + CONVERT( + decimal(9, 6), + COALESCE( + ImpuestoIVA.TasaOCuota, + CASE + WHEN PS.CveObjetoImpuesto = 1 THEN 0 + WHEN PS.IVA IS NULL OR PS.IVA = -1 THEN 0.16 + ELSE PS.IVA + END)) AS PorcentajeIVA, + Costo.FechaInicioVigencia, + Costo.FechaFinVigencia, + CONVERT( + decimal(18, 2), + ROUND( + D.Cantidad + * COALESCE(Costo.Importe, PS.Precio) + * COALESCE(ImpuestoIVA.TasaOCuota, 0), + 2)) AS ImporteIVA, + CONVERT( + decimal(18, 2), + ROUND( + D.Cantidad + * COALESCE(Costo.Importe, PS.Precio), + 2) + + ROUND( + D.Cantidad + * COALESCE(Costo.Importe, PS.Precio) + * COALESCE(ImpuestoIVA.TasaOCuota, 0), + 2)) AS Total + FROM dbo.Tra_PagosDetalle AS D + INNER JOIN dbo.Tra_Pagos AS P + ON P.CveTramitePago = D.CveTramitePago + INNER JOIN dbo.ProductosServicios AS PS + ON PS.CvePS = D.CvePS + LEFT JOIN dbo.UnidadesMedida AS UM + ON UM.CveUnidadMedida = D.CveUM + OUTER APPLY + ( + SELECT TOP (1) + CT.CveCostoTramite, + CT.Importe, + CT.CveObjetoImpuesto, + CT.FechaInicioVigencia, + CT.FechaFinVigencia + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = P.CveTipoTramite + AND CT.CvePS = D.CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, GETDATE()) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, GETDATE()) + ) + ORDER BY + ISNULL(CT.FechaInicioVigencia, CONVERT(date, '19000101')) DESC, + CT.CveCostoTramite DESC + ) AS Costo + OUTER APPLY + ( + SELECT TOP (1) CTI.TasaOCuota + FROM dbo.Tra_CostosTramiteImpuestos AS CTI + WHERE CTI.CveCostoTramite = Costo.CveCostoTramite + AND CTI.EsRetencion = 0 + AND CTI.Impuesto = '002' + AND CTI.TipoFactor = 'Tasa' + ) AS ImpuestoIVA + INNER JOIN dbo.Gral_SAT_ObjetoImpuesto AS OI + ON OI.CveObjetoImpuesto = + COALESCE(Costo.CveObjetoImpuesto, PS.CveObjetoImpuesto) + WHERE D.CveTramitePago = @CveTramitePago + ORDER BY D.CveTramitePagoDetalle; + + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al consultar los conceptos del pago; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Tra_PagoDetalleList'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Tra_PagoDetalleGet + @CveTramitePagoDetalle 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 + D.CveTramitePagoDetalle, + D.CveTramitePago, + P.Nombre AS Pago, + D.CvePS, + PS.Codigo, + PS.PS, + D.CveUM, + UM.UnidadMedida, + UM.Abreviatura, + D.Cantidad, + CONVERT(decimal(18, 2), PS.Precio) AS PrecioProducto, + CONVERT(decimal(18, 2), COALESCE(Costo.Importe, PS.Precio)) AS Importe, + COALESCE(Costo.CveObjetoImpuesto, PS.CveObjetoImpuesto) AS CveObjetoImpuesto, + OI.CodigoObjetoImpuesto, + OI.CodigoObjetoImpuesto COLLATE Modern_Spanish_CS_AS + ' - ' + OI.ObjetoImpuesto AS ObjetoImpuesto, + CONVERT( + decimal(9, 6), + COALESCE( + ImpuestoIVA.TasaOCuota, + CASE + WHEN PS.CveObjetoImpuesto = 1 THEN 0 + WHEN PS.IVA IS NULL OR PS.IVA = -1 THEN 0.16 + ELSE PS.IVA + END)) AS PorcentajeIVA, + Costo.FechaInicioVigencia, + Costo.FechaFinVigencia, + CONVERT( + decimal(18, 2), + ROUND( + D.Cantidad + * COALESCE(Costo.Importe, PS.Precio) + * COALESCE(ImpuestoIVA.TasaOCuota, 0), + 2)) AS ImporteIVA, + CONVERT( + decimal(18, 2), + ROUND(D.Cantidad * COALESCE(Costo.Importe, PS.Precio), 2) + + ROUND( + D.Cantidad + * COALESCE(Costo.Importe, PS.Precio) + * COALESCE(ImpuestoIVA.TasaOCuota, 0), + 2)) AS Total + FROM dbo.Tra_PagosDetalle AS D + INNER JOIN dbo.Tra_Pagos AS P + ON P.CveTramitePago = D.CveTramitePago + INNER JOIN dbo.ProductosServicios AS PS + ON PS.CvePS = D.CvePS + LEFT JOIN dbo.UnidadesMedida AS UM + ON UM.CveUnidadMedida = D.CveUM + OUTER APPLY + ( + SELECT TOP (1) + CT.CveCostoTramite, + CT.Importe, + CT.CveObjetoImpuesto, + CT.FechaInicioVigencia, + CT.FechaFinVigencia + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = P.CveTipoTramite + AND CT.CvePS = D.CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, GETDATE()) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, GETDATE()) + ) + ORDER BY + ISNULL(CT.FechaInicioVigencia, CONVERT(date, '19000101')) DESC, + CT.CveCostoTramite DESC + ) AS Costo + OUTER APPLY + ( + SELECT TOP (1) CTI.TasaOCuota + FROM dbo.Tra_CostosTramiteImpuestos AS CTI + WHERE CTI.CveCostoTramite = Costo.CveCostoTramite + AND CTI.EsRetencion = 0 + AND CTI.Impuesto = '002' + AND CTI.TipoFactor = 'Tasa' + ) AS ImpuestoIVA + INNER JOIN dbo.Gral_SAT_ObjetoImpuesto AS OI + ON OI.CveObjetoImpuesto = + COALESCE(Costo.CveObjetoImpuesto, PS.CveObjetoImpuesto) + WHERE D.CveTramitePagoDetalle = @CveTramitePagoDetalle; + + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al consultar el concepto del pago; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Tra_PagoDetalleGet'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO + + +/* + Configuración fiscal versionada para costos de trámites y fotografía de + impuestos por concepto de venta. + + Este script no administra transacciones. La transacción de las operaciones + de la aplicación permanece en los servicios C#. +*/ + +UPDATE dbo.ProductosServicios +SET CveObjetoImpuesto = 2, + IVA = NULL +WHERE CvePS = 9; +GO + +IF COL_LENGTH(N'dbo.Tra_CostosTramite', N'CveObjetoImpuesto') IS NULL +BEGIN + ALTER TABLE dbo.Tra_CostosTramite + ADD CveObjetoImpuesto tinyint NULL; + + UPDATE CT + SET CveObjetoImpuesto = ISNULL(PS.CveObjetoImpuesto, 1) + FROM dbo.Tra_CostosTramite AS CT + INNER JOIN dbo.ProductosServicios AS PS + ON PS.CvePS = CT.CvePS; + + UPDATE dbo.Tra_CostosTramite + SET CveObjetoImpuesto = 1 + WHERE CveObjetoImpuesto IS NULL; + + ALTER TABLE dbo.Tra_CostosTramite + ALTER COLUMN CveObjetoImpuesto tinyint NOT NULL; + + ALTER TABLE dbo.Tra_CostosTramite + ADD CONSTRAINT DF_Tra_CostosTramite_CveObjetoImpuesto + DEFAULT (1) FOR CveObjetoImpuesto; +END; +GO + +IF NOT EXISTS +( + SELECT 1 + FROM sys.foreign_keys + WHERE parent_object_id = OBJECT_ID(N'dbo.Tra_CostosTramite') + AND name = N'FK_Tra_CostosTramite_Gral_ObjetoImpuesto' +) +BEGIN + ALTER TABLE dbo.Tra_CostosTramite WITH CHECK + ADD CONSTRAINT FK_Tra_CostosTramite_Gral_ObjetoImpuesto + FOREIGN KEY (CveObjetoImpuesto) + REFERENCES dbo.Gral_SAT_ObjetoImpuesto (CveObjetoImpuesto); +END; +GO + +IF OBJECT_ID(N'dbo.Tra_CostosTramiteImpuestos', N'U') IS NULL +BEGIN + CREATE TABLE dbo.Tra_CostosTramiteImpuestos + ( + CveCostoTramiteImpuesto int IDENTITY(1, 1) NOT NULL, + CveCostoTramite int NOT NULL, + EsRetencion bit CONSTRAINT DF_Tra_CostosTramiteImpuestos_EsRetencion + DEFAULT (0) NOT NULL, + Impuesto varchar(3) COLLATE Modern_Spanish_CS_AS NOT NULL, + TipoFactor varchar(6) COLLATE Modern_Spanish_CS_AS NOT NULL, + TasaOCuota decimal(19, 6) NULL, + FechaAlta datetime CONSTRAINT DF_Tra_CostosTramiteImpuestos_FechaAlta + DEFAULT (GETDATE()) NOT NULL, + CveUsuario int NOT NULL, + CONSTRAINT PK_Tra_CostosTramiteImpuestos + PRIMARY KEY CLUSTERED (CveCostoTramiteImpuesto), + CONSTRAINT FK_Tra_CostosTramiteImpuestos_Costo + FOREIGN KEY (CveCostoTramite) + REFERENCES dbo.Tra_CostosTramite (CveCostoTramite), + CONSTRAINT FK_Tra_CostosTramiteImpuestos_Usuario + FOREIGN KEY (CveUsuario) + REFERENCES dbo.Sis_Usuarios (CveUsuario), + CONSTRAINT CK_Tra_CostosTramiteImpuestos_TipoFactor + CHECK (TipoFactor IN ('Tasa', 'Cuota', 'Exento')), + CONSTRAINT CK_Tra_CostosTramiteImpuestos_Tasa + CHECK + ( + (TipoFactor = 'Exento' AND TasaOCuota IS NULL) + OR + (TipoFactor <> 'Exento' AND TasaOCuota >= 0) + ) + ); + + CREATE UNIQUE NONCLUSTERED INDEX + UX_Tra_CostosTramiteImpuestos_Configuracion + ON dbo.Tra_CostosTramiteImpuestos + ( + CveCostoTramite, + EsRetencion, + Impuesto, + TipoFactor, + TasaOCuota + ); +END; +GO + +IF COL_LENGTH(N'dbo.VentasDetalle', N'CveObjetoImpuesto') IS NULL +BEGIN + ALTER TABLE dbo.VentasDetalle + ADD CveObjetoImpuesto tinyint NULL; + + UPDATE VD + SET CveObjetoImpuesto = COALESCE( + CT.CveObjetoImpuesto, + PS.CveObjetoImpuesto, + 1) + FROM dbo.VentasDetalle AS VD + INNER JOIN dbo.ProductosServicios AS PS + ON PS.CvePS = VD.CvePS + LEFT JOIN dbo.Tra_CostosTramite AS CT + ON CT.CveCostoTramite = VD.CveCostoTramite; + + UPDATE dbo.VentasDetalle + SET CveObjetoImpuesto = 1 + WHERE CveObjetoImpuesto IS NULL; + + ALTER TABLE dbo.VentasDetalle + ALTER COLUMN CveObjetoImpuesto tinyint NOT NULL; + + ALTER TABLE dbo.VentasDetalle + ADD CONSTRAINT DF_VentasDetalle_CveObjetoImpuesto + DEFAULT (1) FOR CveObjetoImpuesto; +END; +GO + +IF NOT EXISTS +( + SELECT 1 + FROM sys.foreign_keys + WHERE parent_object_id = OBJECT_ID(N'dbo.VentasDetalle') + AND name = N'FK_VentasDetalle_Gral_ObjetoImpuesto' +) +BEGIN + ALTER TABLE dbo.VentasDetalle WITH CHECK + ADD CONSTRAINT FK_VentasDetalle_Gral_ObjetoImpuesto + FOREIGN KEY (CveObjetoImpuesto) + REFERENCES dbo.Gral_SAT_ObjetoImpuesto (CveObjetoImpuesto); +END; +GO + +IF OBJECT_ID(N'dbo.VentasDetalleImpuestos', N'U') IS NULL +BEGIN + CREATE TABLE dbo.VentasDetalleImpuestos + ( + CveVentaDetalleImpuesto int IDENTITY(1, 1) NOT NULL, + CveVentaDetalle int NOT NULL, + EsRetencion bit CONSTRAINT DF_VentasDetalleImpuestos_EsRetencion + DEFAULT (0) NOT NULL, + Base decimal(19, 6) NOT NULL, + Impuesto varchar(3) COLLATE Modern_Spanish_CS_AS NOT NULL, + TipoFactor varchar(6) COLLATE Modern_Spanish_CS_AS NOT NULL, + TasaOCuota decimal(19, 6) NULL, + Importe decimal(18, 2) CONSTRAINT DF_VentasDetalleImpuestos_Importe + DEFAULT (0) NOT NULL, + FechaAlta datetime CONSTRAINT DF_VentasDetalleImpuestos_FechaAlta + DEFAULT (GETDATE()) NOT NULL, + CveUsuario int NOT NULL, + CONSTRAINT PK_VentasDetalleImpuestos + PRIMARY KEY CLUSTERED (CveVentaDetalleImpuesto), + CONSTRAINT FK_VentasDetalleImpuestos_VentaDetalle + FOREIGN KEY (CveVentaDetalle) + REFERENCES dbo.VentasDetalle (CveVentaDetalle), + CONSTRAINT FK_VentasDetalleImpuestos_Usuario + FOREIGN KEY (CveUsuario) + REFERENCES dbo.Sis_Usuarios (CveUsuario), + CONSTRAINT CK_VentasDetalleImpuestos_Importes + CHECK (Base >= 0 AND Importe >= 0), + CONSTRAINT CK_VentasDetalleImpuestos_TipoFactor + CHECK (TipoFactor IN ('Tasa', 'Cuota', 'Exento')), + CONSTRAINT CK_VentasDetalleImpuestos_Tasa + CHECK + ( + (TipoFactor = 'Exento' AND TasaOCuota IS NULL) + OR + (TipoFactor <> 'Exento' AND TasaOCuota >= 0) + ) + ); + + CREATE UNIQUE NONCLUSTERED INDEX + UX_VentasDetalleImpuestos_Configuracion + ON dbo.VentasDetalleImpuestos + ( + CveVentaDetalle, + EsRetencion, + Impuesto, + TipoFactor, + TasaOCuota + ); +END; +GO + +UPDATE CT +SET CveObjetoImpuesto = 2 +FROM dbo.Tra_CostosTramite AS CT +WHERE CT.CveCostoTramite = 11; +GO + +INSERT dbo.Tra_CostosTramiteImpuestos +( + CveCostoTramite, + EsRetencion, + Impuesto, + TipoFactor, + TasaOCuota, + FechaAlta, + CveUsuario +) +SELECT + CT.CveCostoTramite, + 0, + '002', + 'Tasa', + CONVERT( + decimal(19, 6), + CASE + WHEN PS.IVA IS NULL OR PS.IVA = -1 THEN 0.16 + ELSE PS.IVA + END), + GETDATE(), + CT.CveUsuario +FROM dbo.Tra_CostosTramite AS CT +INNER JOIN dbo.ProductosServicios AS PS + ON PS.CvePS = CT.CvePS +WHERE CT.CveObjetoImpuesto <> 1 + AND CASE + WHEN PS.IVA IS NULL OR PS.IVA = -1 THEN 0.16 + ELSE PS.IVA + END > 0 + AND NOT EXISTS + ( + SELECT 1 + FROM dbo.Tra_CostosTramiteImpuestos AS CTI + WHERE CTI.CveCostoTramite = CT.CveCostoTramite + AND CTI.EsRetencion = 0 + AND CTI.Impuesto = '002' + ); +GO + +DELETE dbo.VentasDetalleImpuestos +WHERE CveVentaDetalle = 6; +GO + +UPDATE dbo.VentasDetalle +SET CveObjetoImpuesto = 2, + IVA = 320.00 +WHERE CveVentaDetalle = 6 + AND CveVenta = 1019; +GO + +INSERT dbo.VentasDetalleImpuestos +( + CveVentaDetalle, + EsRetencion, + Base, + Impuesto, + TipoFactor, + TasaOCuota, + Importe, + FechaAlta, + CveUsuario +) +SELECT + VD.CveVentaDetalle, + 0, + CONVERT(decimal(19, 6), VD.Importe - VD.Descuento), + '002', + 'Tasa', + CONVERT( + decimal(19, 6), + CASE + WHEN VD.Importe - VD.Descuento = 0 THEN 0 + ELSE VD.IVA / (VD.Importe - VD.Descuento) + END), + VD.IVA, + GETDATE(), + VD.CveUsuario +FROM dbo.VentasDetalle AS VD +WHERE VD.CveVentaDetalle = 6 + AND VD.CveVenta = 1019; +GO + +UPDATE dbo.Ventas +SET SubTotal = 2000.00, + Descuento = 0.00, + IVA = 320.00, + Total = 2320.00 +WHERE CveVenta = 1019 + AND EXISTS + ( + SELECT 1 + FROM dbo.VentasDetalle + WHERE CveVentaDetalle = 6 + AND CveVenta = 1019 + ); +GO + +DECLARE @CveTablaProductoServicio int; + +SELECT @CveTablaProductoServicio = CveTabla +FROM dbo.Bit_Tablas +WHERE NombreTablaSQL = 'ProductosServicios' + COLLATE Modern_Spanish_CS_AS; + +IF @CveTablaProductoServicio IS NOT NULL +BEGIN + DECLARE @CamposFiscalesProducto TABLE + ( + NombreCampoSQL varchar(128) COLLATE Modern_Spanish_CS_AS NOT NULL, + NombreMostrar varchar(150) COLLATE Modern_Spanish_CS_AS NOT NULL + ); + + INSERT @CamposFiscalesProducto + ( + NombreCampoSQL, + NombreMostrar + ) + VALUES + ('CveObjetoImpuesto', 'Objeto de impuesto'), + ('IVA', 'Tratamiento de IVA'); + + UPDATE BC + SET NombreMostrar = C.NombreMostrar, + MostrarEnBitacora = 1, + TipoDatoSQL = TY.name COLLATE Modern_Spanish_CS_AS, + Activo = 1, + FechaMod = GETDATE() + FROM dbo.Bit_Campos AS BC + INNER JOIN @CamposFiscalesProducto AS C + ON C.NombreCampoSQL = BC.NombreCampoSQL + INNER JOIN sys.columns AS SC + ON SC.object_id = OBJECT_ID(N'dbo.ProductosServicios') + AND SC.name COLLATE Modern_Spanish_CS_AS = C.NombreCampoSQL + INNER JOIN sys.types AS TY + ON TY.user_type_id = SC.user_type_id + WHERE BC.CveTabla = @CveTablaProductoServicio; + + INSERT dbo.Bit_Campos + ( + CveTabla, + NombreCampoSQL, + NombreMostrar, + MostrarEnBitacora, + TipoDatoSQL, + Activo + ) + SELECT + @CveTablaProductoServicio, + C.NombreCampoSQL, + C.NombreMostrar, + 1, + TY.name COLLATE Modern_Spanish_CS_AS, + 1 + FROM @CamposFiscalesProducto AS C + INNER JOIN sys.columns AS SC + ON SC.object_id = OBJECT_ID(N'dbo.ProductosServicios') + AND SC.name COLLATE Modern_Spanish_CS_AS = C.NombreCampoSQL + INNER JOIN sys.types AS TY + ON TY.user_type_id = SC.user_type_id + WHERE NOT EXISTS + ( + SELECT 1 + FROM dbo.Bit_Campos AS BC + WHERE BC.CveTabla = @CveTablaProductoServicio + AND BC.NombreCampoSQL = C.NombreCampoSQL + ); +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Gral_ProductoServicioList + @Texto varchar(200) = NULL, + @IncluirInactivos bit = 0, + @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 + SET @Texto = NULLIF(LTRIM(RTRIM(@Texto)), ''); + + SELECT + PS.CvePS, + PS.Codigo, + PS.PS, + PS.CveTipoPS, + CASE PS.CveTipoPS + WHEN 2 THEN 'Producto' + ELSE 'Servicio' + END AS TipoPS, + PS.CveClasificacion, + C.Clasificacion, + PS.CveUnidadMedida, + UM.UnidadMedida, + CONVERT(decimal(18, 2), PS.Precio) AS Precio, + PS.CveObjetoImpuesto, + OI.ObjetoImpuesto, + OI.CodigoObjetoImpuesto, + CONVERT(decimal(9, 6), PS.IVA) AS IVA, + CASE + WHEN PS.CveObjetoImpuesto = 1 THEN 'Sin IVA' + WHEN PS.IVA = 0 THEN 'Sin IVA' + ELSE 'IVA general' + END AS TratamientoIVA, + PS.Estado AS EsActivo + FROM dbo.ProductosServicios AS PS + INNER JOIN dbo.Clasificaciones AS C + ON C.CveClasificacion = PS.CveClasificacion + INNER JOIN dbo.UnidadesMedida AS UM + ON UM.CveUnidadMedida = PS.CveUnidadMedida + INNER JOIN dbo.Gral_SAT_ObjetoImpuesto AS OI + ON OI.CveObjetoImpuesto = PS.CveObjetoImpuesto + WHERE (@IncluirInactivos = 1 OR PS.Estado = 1) + AND + ( + @Texto IS NULL + OR PS.Codigo LIKE '%' + @Texto + '%' + OR PS.PS LIKE '%' + @Texto + '%' + OR C.Clasificacion LIKE '%' + @Texto + '%' + OR UM.UnidadMedida LIKE '%' + @Texto + '%' + ) + ORDER BY PS.PS, PS.CvePS; + + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al consultar los productos y servicios; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Gral_ProductoServicioList'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Gral_ProductoServicioGet + @CvePS 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 + PS.CvePS, + PS.Codigo, + PS.PS, + PS.CveTipoPS, + CASE PS.CveTipoPS + WHEN 2 THEN 'Producto' + ELSE 'Servicio' + END AS TipoPS, + PS.CveClasificacion, + C.Clasificacion, + PS.CveUnidadMedida, + UM.UnidadMedida, + CONVERT(decimal(18, 2), PS.Precio) AS Precio, + PS.CveObjetoImpuesto, + OI.ObjetoImpuesto, + OI.CodigoObjetoImpuesto, + CONVERT(decimal(9, 6), PS.IVA) AS IVA, + CASE + WHEN PS.CveObjetoImpuesto = 1 THEN 'Sin IVA' + WHEN PS.IVA = 0 THEN 'Sin IVA' + ELSE 'IVA general' + END AS TratamientoIVA, + PS.Estado AS EsActivo + FROM dbo.ProductosServicios AS PS + INNER JOIN dbo.Clasificaciones AS C + ON C.CveClasificacion = PS.CveClasificacion + INNER JOIN dbo.UnidadesMedida AS UM + ON UM.CveUnidadMedida = PS.CveUnidadMedida + INNER JOIN dbo.Gral_SAT_ObjetoImpuesto AS OI + ON OI.CveObjetoImpuesto = PS.CveObjetoImpuesto + WHERE PS.CvePS = @CvePS; + + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al consultar el producto o servicio; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Gral_ProductoServicioGet'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Gral_ProductoServicioOpcionesList + @CveClasificacionActual int = NULL, + @CveUnidadMedidaActual int = NULL, + @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 + CveClasificacion, + CASE + WHEN NULLIF(LTRIM(RTRIM(Codigo)), '') IS NULL + THEN Clasificacion COLLATE Modern_Spanish_CS_AS + ELSE Codigo COLLATE Modern_Spanish_CS_AS + + ' - ' + + Clasificacion COLLATE Modern_Spanish_CS_AS + END AS Clasificacion + FROM dbo.Clasificaciones + WHERE (Estado = 1 AND Afectable = 1) + OR CveClasificacion = @CveClasificacionActual + ORDER BY Clasificacion, Codigo, CveClasificacion; + + SELECT + CveUnidadMedida, + CASE + WHEN NULLIF(LTRIM(RTRIM(Abreviatura)), '') IS NULL + THEN UnidadMedida COLLATE Modern_Spanish_CS_AS + ELSE UnidadMedida COLLATE Modern_Spanish_CS_AS + + ' (' + + Abreviatura COLLATE Modern_Spanish_CS_AS + + ')' + END AS UnidadMedida + FROM dbo.UnidadesMedida + WHERE Estado = 1 + OR CveUnidadMedida = @CveUnidadMedidaActual + ORDER BY UnidadMedida, CveUnidadMedida; + + SELECT + CveObjetoImpuesto, + CodigoObjetoImpuesto COLLATE Modern_Spanish_CS_AS + ' - ' + ObjetoImpuesto AS ObjetoImpuesto + FROM dbo.Gral_SAT_ObjetoImpuesto + ORDER BY CodigoObjetoImpuesto; + + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al consultar las opciones del catálogo; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Gral_ProductoServicioOpcionesList'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Gral_ProductoServicioSave + @CvePS int = NULL, + @Codigo varchar(50) = NULL, + @PS varchar(846), + @CveTipoPS int, + @CveClasificacion int, + @CveUnidadMedida int, + @Precio decimal(18, 2), + @CveObjetoImpuesto tinyint, + @IVA decimal(9, 6) = NULL, + @EsActivo bit, + @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 + @CvePS = NULLIF(@CvePS, 0), + @Codigo = NULLIF(LTRIM(RTRIM(@Codigo)), ''), + @PS = LTRIM(RTRIM(@PS)); + + IF NULLIF(@PS, '') IS NULL + BEGIN + SELECT + @ErrorMostrar = 'Capture la descripción del producto o servicio.', + @ErrorTipo = 2; + RETURN; + END; + + IF @CveTipoPS NOT IN (1, 2) + BEGIN + SELECT + @ErrorMostrar = 'Seleccione si el registro es producto o servicio.', + @ErrorTipo = 2; + RETURN; + END; + + IF @Precio < 0 + BEGIN + SELECT + @ErrorMostrar = 'El precio no puede ser negativo.', + @ErrorTipo = 2; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.Gral_SAT_ObjetoImpuesto + WHERE CveObjetoImpuesto = @CveObjetoImpuesto + ) + BEGIN + SELECT + @ErrorMostrar = 'Seleccione un objeto de impuesto válido.', + @ErrorTipo = 2; + RETURN; + END; + + IF @IVA NOT IN (-1, 0) AND @IVA IS NOT NULL + BEGIN + SELECT + @ErrorMostrar = 'Seleccione IVA general o sin IVA.', + @ErrorTipo = 2; + RETURN; + END; + + IF @CveObjetoImpuesto = 1 + SET @IVA = 0; + + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.Clasificaciones + WHERE CveClasificacion = @CveClasificacion + AND + ( + Estado = 1 + OR CveClasificacion = + ( + SELECT CveClasificacion + FROM dbo.ProductosServicios + WHERE CvePS = @CvePS + ) + ) + ) + OR NOT EXISTS + ( + SELECT 1 + FROM dbo.UnidadesMedida + WHERE CveUnidadMedida = @CveUnidadMedida + AND + ( + Estado = 1 + OR CveUnidadMedida = + ( + SELECT CveUnidadMedida + FROM dbo.ProductosServicios + WHERE CvePS = @CvePS + ) + ) + ) + BEGIN + SELECT + @ErrorMostrar = 'Seleccione una clasificación y una unidad de medida vigentes.', + @ErrorTipo = 2; + RETURN; + END; + + IF @Codigo IS NOT NULL + AND EXISTS + ( + SELECT 1 + FROM dbo.ProductosServicios + WHERE Codigo = @Codigo + AND CvePS <> ISNULL(@CvePS, 0) + ) + BEGIN + SELECT + @ErrorMostrar = 'El código ya está asignado a otro producto o servicio.', + @ErrorTipo = 2; + RETURN; + END; + + IF @CvePS IS NULL + BEGIN + INSERT dbo.ProductosServicios + ( + CveClasificacion, + CveUnidadMedida, + Codigo, + PS, + Precio, + Estado, + Fecha, + CveTipoPS, + CveObjetoImpuesto, + IVA, + CveUsuario, + CveUsuarioModifico, + FechaUltimaModificacion + ) + VALUES + ( + @CveClasificacion, + @CveUnidadMedida, + @Codigo, + @PS, + CONVERT(float, @Precio), + @EsActivo, + GETDATE(), + @CveTipoPS, + @CveObjetoImpuesto, + @IVA, + @CveUsuarioEjecuta, + @CveUsuarioEjecuta, + GETDATE() + ); + + SET @CvePS = CONVERT(int, SCOPE_IDENTITY()); + END + ELSE + BEGIN + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.ProductosServicios + WHERE CvePS = @CvePS + ) + BEGIN + SELECT + @ErrorMostrar = 'El producto o servicio seleccionado no existe.', + @ErrorTipo = 2; + RETURN; + END; + + UPDATE dbo.ProductosServicios + SET CveClasificacion = @CveClasificacion, + CveUnidadMedida = @CveUnidadMedida, + Codigo = @Codigo, + PS = @PS, + Precio = CONVERT(float, @Precio), + Estado = @EsActivo, + CveTipoPS = @CveTipoPS, + CveObjetoImpuesto = @CveObjetoImpuesto, + IVA = @IVA, + CveUsuarioModifico = @CveUsuarioEjecuta, + FechaUltimaModificacion = GETDATE() + WHERE CvePS = @CvePS; + END; + + SELECT @CvePS AS CvePS; + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al guardar el producto o servicio; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Gral_ProductoServicioSave'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Tra_PagoDetalleSave + @CveTramitePagoDetalle int = NULL, + @CveTramitePago int, + @CvePS int, + @CveUM int, + @Cantidad decimal(18, 4), + @Importe decimal(18, 2) = NULL, + @CveObjetoImpuesto tinyint, + @PorcentajeIVA decimal(9, 6), + @FechaInicioVigencia date = NULL, + @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 + @CveTipoTramite int, + @CveMoneda int, + @Descripcion varchar(300), + @PrecioProducto decimal(18, 2), + @CveCostoVigente int, + @CveCostoObjetivo int, + @FechaInicioVigente date, + @ImporteVigente decimal(18, 2), + @CveObjetoImpuestoVigente tinyint, + @PorcentajeIVAVigente decimal(9, 6); + + SELECT + @CveTipoTramite = P.CveTipoTramite + FROM dbo.Tra_Pagos AS P + WHERE P.CveTramitePago = @CveTramitePago; + + SELECT + @PrecioProducto = CONVERT(decimal(18, 2), PS.Precio), + @Descripcion = LEFT(PS.PS, 300) + FROM dbo.ProductosServicios AS PS + WHERE PS.CvePS = @CvePS + AND PS.Estado = 1; + + SELECT TOP (1) + @CveMoneda = M.CveMoneda + FROM dbo.Gral_Monedas AS M + WHERE M.AbreviaturaSAT = 'MXN' + ORDER BY M.CveMoneda; + + SET @Importe = ISNULL(@Importe, @PrecioProducto); + SET @FechaInicioVigencia = + ISNULL(@FechaInicioVigencia, CONVERT(date, GETDATE())); + + IF @CveTipoTramite IS NULL + OR @PrecioProducto IS NULL + OR @CveMoneda IS NULL + BEGIN + SELECT + @ErrorMostrar = 'El pago, producto o moneda seleccionados no están disponibles.', + @ErrorTipo = 2; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.UnidadesMedida + WHERE CveUnidadMedida = @CveUM + AND Estado = 1 + ) + BEGIN + SELECT + @ErrorMostrar = 'Seleccione una unidad de medida vigente.', + @ErrorTipo = 2; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.Gral_SAT_ObjetoImpuesto + WHERE CveObjetoImpuesto = @CveObjetoImpuesto + ) + BEGIN + SELECT + @ErrorMostrar = 'Seleccione un objeto de impuesto válido.', + @ErrorTipo = 2; + RETURN; + END; + + IF @Cantidad <= 0 + OR @Importe < 0 + OR @PorcentajeIVA < 0 + OR @PorcentajeIVA > 1 + BEGIN + SELECT + @ErrorMostrar = 'Capture cantidad, importe y porcentaje de IVA válidos.', + @ErrorTipo = 2; + RETURN; + END; + + IF @CveObjetoImpuesto = 1 + SET @PorcentajeIVA = 0; + + SET @CveTramitePagoDetalle = NULLIF(@CveTramitePagoDetalle, 0); + + IF @CveTramitePagoDetalle IS NULL + BEGIN + INSERT dbo.Tra_PagosDetalle + ( + CveTramitePago, + CvePS, + CveUM, + Cantidad, + FechaAlta, + CveUsuario + ) + VALUES + ( + @CveTramitePago, + @CvePS, + @CveUM, + @Cantidad, + GETDATE(), + @CveUsuarioEjecuta + ); + + SET @CveTramitePagoDetalle = CONVERT(int, SCOPE_IDENTITY()); + END + ELSE + BEGIN + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.Tra_PagosDetalle + WHERE CveTramitePagoDetalle = @CveTramitePagoDetalle + AND CveTramitePago = @CveTramitePago + ) + BEGIN + SELECT + @ErrorMostrar = 'El concepto de pago seleccionado no existe.', + @ErrorTipo = 2; + RETURN; + END; + + UPDATE dbo.Tra_PagosDetalle + SET CvePS = @CvePS, + CveUM = @CveUM, + Cantidad = @Cantidad, + FechaModificacion = GETDATE(), + CveUsuario = @CveUsuarioEjecuta + WHERE CveTramitePagoDetalle = @CveTramitePagoDetalle; + END; + + SELECT TOP (1) + @CveCostoVigente = CT.CveCostoTramite, + @FechaInicioVigente = CT.FechaInicioVigencia, + @ImporteVigente = CT.Importe, + @CveObjetoImpuestoVigente = CT.CveObjetoImpuesto, + @PorcentajeIVAVigente = IVA.TasaOCuota + FROM dbo.Tra_CostosTramite AS CT + OUTER APPLY + ( + SELECT TOP (1) CTI.TasaOCuota + FROM dbo.Tra_CostosTramiteImpuestos AS CTI + WHERE CTI.CveCostoTramite = CT.CveCostoTramite + AND CTI.EsRetencion = 0 + AND CTI.Impuesto = '002' + AND CTI.TipoFactor = 'Tasa' + ) AS IVA + WHERE CT.CveTipoTramite = @CveTipoTramite + AND CT.CvePS = @CvePS + AND CT.EsActivo = 1 + AND CT.FechaFinVigencia IS NULL + ORDER BY + ISNULL(CT.FechaInicioVigencia, CONVERT(date, '19000101')) DESC, + CT.CveCostoTramite DESC; + + IF @CveCostoVigente IS NULL + BEGIN + INSERT dbo.Tra_CostosTramite + ( + CveTipoTramite, + CvePS, + CveMoneda, + Descripcion, + Importe, + CveObjetoImpuesto, + FechaInicioVigencia, + FechaFinVigencia, + EsActivo, + CveUsuario + ) + VALUES + ( + @CveTipoTramite, + @CvePS, + @CveMoneda, + @Descripcion, + @Importe, + @CveObjetoImpuesto, + @FechaInicioVigencia, + NULL, + 1, + @CveUsuarioEjecuta + ); + + SET @CveCostoObjetivo = CONVERT(int, SCOPE_IDENTITY()); + END + ELSE IF @FechaInicioVigente IS NOT NULL + AND @FechaInicioVigencia < @FechaInicioVigente + BEGIN + SELECT + @ErrorMostrar = 'La nueva vigencia no puede iniciar antes de la vigencia actual.', + @ErrorTipo = 2; + RETURN; + END + ELSE IF @FechaInicioVigente = @FechaInicioVigencia + BEGIN + UPDATE dbo.Tra_CostosTramite + SET CveMoneda = @CveMoneda, + Descripcion = @Descripcion, + Importe = @Importe, + CveObjetoImpuesto = @CveObjetoImpuesto, + FechaInicioVigencia = @FechaInicioVigencia, + FechaModificacion = GETDATE(), + CveUsuario = @CveUsuarioEjecuta + WHERE CveCostoTramite = @CveCostoVigente; + + SET @CveCostoObjetivo = @CveCostoVigente; + END + ELSE IF @ImporteVigente <> @Importe + OR @CveObjetoImpuestoVigente <> @CveObjetoImpuesto + OR ISNULL(@PorcentajeIVAVigente, 0) <> @PorcentajeIVA + BEGIN + UPDATE dbo.Tra_CostosTramite + SET FechaFinVigencia = DATEADD(day, -1, @FechaInicioVigencia), + FechaModificacion = GETDATE(), + CveUsuario = @CveUsuarioEjecuta + WHERE CveCostoTramite = @CveCostoVigente; + + INSERT dbo.Tra_CostosTramite + ( + CveTipoTramite, + CvePS, + CveMoneda, + Descripcion, + Importe, + CveObjetoImpuesto, + FechaInicioVigencia, + FechaFinVigencia, + EsActivo, + CveUsuario + ) + VALUES + ( + @CveTipoTramite, + @CvePS, + @CveMoneda, + @Descripcion, + @Importe, + @CveObjetoImpuesto, + @FechaInicioVigencia, + NULL, + 1, + @CveUsuarioEjecuta + ); + + SET @CveCostoObjetivo = CONVERT(int, SCOPE_IDENTITY()); + + INSERT dbo.Tra_CostosTramiteImpuestos + ( + CveCostoTramite, + EsRetencion, + Impuesto, + TipoFactor, + TasaOCuota, + FechaAlta, + CveUsuario + ) + SELECT + @CveCostoObjetivo, + CTI.EsRetencion, + CTI.Impuesto, + CTI.TipoFactor, + CTI.TasaOCuota, + GETDATE(), + @CveUsuarioEjecuta + FROM dbo.Tra_CostosTramiteImpuestos AS CTI + WHERE CTI.CveCostoTramite = @CveCostoVigente + AND NOT + ( + CTI.EsRetencion = 0 + AND CTI.Impuesto = '002' + ); + END + ELSE + BEGIN + SET @CveCostoObjetivo = @CveCostoVigente; + END; + + DELETE dbo.Tra_CostosTramiteImpuestos + WHERE CveCostoTramite = @CveCostoObjetivo + AND EsRetencion = 0 + AND Impuesto = '002'; + + IF @CveObjetoImpuesto <> 1 AND @PorcentajeIVA > 0 + BEGIN + INSERT dbo.Tra_CostosTramiteImpuestos + ( + CveCostoTramite, + EsRetencion, + Impuesto, + TipoFactor, + TasaOCuota, + FechaAlta, + CveUsuario + ) + VALUES + ( + @CveCostoObjetivo, + 0, + '002', + 'Tasa', + @PorcentajeIVA, + GETDATE(), + @CveUsuarioEjecuta + ); + END; + + SELECT @CveTramitePagoDetalle AS CveTramitePagoDetalle; + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al guardar el concepto del pago; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Tra_PagoDetalleSave'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Vta_TramiteVentaTotalesUpdate + @CveVenta 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 + DECLARE + @SubTotal decimal(18, 2), + @Descuento decimal(18, 2), + @IVA decimal(18, 2), + @IEPS decimal(18, 2), + @RetIVA decimal(18, 2), + @RetISR decimal(18, 2), + @Traslados decimal(18, 2), + @Retenciones decimal(18, 2), + @Total decimal(18, 2); + + SELECT + @SubTotal = ISNULL(SUM(VD.Importe), 0), + @Descuento = ISNULL(SUM(VD.Descuento), 0) + FROM dbo.VentasDetalle AS VD + WHERE VD.CveVenta = @CveVenta; + + SELECT + @IVA = ISNULL(SUM( + CASE + WHEN VDI.EsRetencion = 0 AND VDI.Impuesto = '002' + THEN VDI.Importe + ELSE 0 + END), 0), + @IEPS = ISNULL(SUM( + CASE + WHEN VDI.EsRetencion = 0 AND VDI.Impuesto = '003' + THEN VDI.Importe + ELSE 0 + END), 0), + @RetIVA = ISNULL(SUM( + CASE + WHEN VDI.EsRetencion = 1 AND VDI.Impuesto = '002' + THEN VDI.Importe + ELSE 0 + END), 0), + @RetISR = ISNULL(SUM( + CASE + WHEN VDI.EsRetencion = 1 AND VDI.Impuesto = '001' + THEN VDI.Importe + ELSE 0 + END), 0), + @Traslados = ISNULL(SUM( + CASE + WHEN VDI.EsRetencion = 0 THEN VDI.Importe + ELSE 0 + END), 0), + @Retenciones = ISNULL(SUM( + CASE + WHEN VDI.EsRetencion = 1 THEN VDI.Importe + ELSE 0 + END), 0) + FROM dbo.VentasDetalle AS VD + INNER JOIN dbo.VentasDetalleImpuestos AS VDI + ON VDI.CveVentaDetalle = VD.CveVentaDetalle + WHERE VD.CveVenta = @CveVenta; + + SET @Total = ROUND( + @SubTotal + - @Descuento + + @Traslados + - @Retenciones, + 2); + + UPDATE dbo.Ventas + SET SubTotal = @SubTotal, + Descuento = @Descuento, + IVA = @IVA, + IEPS = @IEPS, + RetIVA = @RetIVA, + RetISR = @RetISR, + Total = @Total, + CveUsuarioModifico = @CveUsuarioEjecuta + WHERE CveVenta = @CveVenta; + + IF @@ROWCOUNT = 0 + BEGIN + SELECT + @ErrorMostrar = 'La venta seleccionada no existe.', + @ErrorTipo = 2; + RETURN; + END; + + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al recalcular los totales de la venta; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Vta_TramiteVentaTotalesUpdate'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Vta_TramiteVentaSave + @CveSolicitud int, + @CveTramitePago int, + @Observaciones varchar(2000) = NULL, + @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 + @CveVendedor int = 1, + @CveCliente int, + @CveTipoTramite int, + @CveSubProyecto int, + @CveEstatusFactura int, + @CveVenta int, + @Folio int, + @Fecha datetime = GETDATE(); + + SELECT + @CveCliente = F.CveCliente, + @CveTipoTramite = S.CveTipoTramite + FROM dbo.Tra_Solicitudes AS S + INNER JOIN dbo.Fraccionamientos AS F + ON F.CveFraccionamiento = S.CveFraccionamiento + INNER JOIN dbo.Tra_Pagos AS P + ON P.CveTramitePago = @CveTramitePago + AND P.CveTipoTramite = S.CveTipoTramite + AND P.EsActivo = 1 + WHERE S.CveSolicitud = @CveSolicitud; + + IF @CveCliente IS NULL + BEGIN + SELECT + @ErrorMostrar = 'El pago seleccionado no corresponde al trámite.', + @ErrorTipo = 2; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.Vta_Vendedores + WHERE CveVendedor = @CveVendedor + ) + BEGIN + SELECT + @ErrorMostrar = 'No se encuentra configurado el vendedor predeterminado 1.', + @ErrorTipo = 2; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.Tra_PagosDetalle + WHERE CveTramitePago = @CveTramitePago + ) + BEGIN + SELECT + @ErrorMostrar = 'El pago seleccionado no tiene conceptos para vender.', + @ErrorTipo = 2; + RETURN; + END; + + IF EXISTS + ( + SELECT 1 + FROM dbo.Tra_PagosDetalle AS D + WHERE D.CveTramitePago = @CveTramitePago + AND NOT EXISTS + ( + SELECT 1 + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = @CveTipoTramite + AND CT.CvePS = D.CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, @Fecha) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, @Fecha) + ) + ) + ) + BEGIN + SELECT + @ErrorMostrar = 'Uno o más conceptos no tienen un costo vigente configurado.', + @ErrorTipo = 2; + RETURN; + END; + + IF EXISTS + ( + SELECT 1 + FROM dbo.Tra_PagosDetalle AS D + CROSS APPLY + ( + SELECT TOP (1) + CT.CveCostoTramite, + CT.CveObjetoImpuesto + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = @CveTipoTramite + AND CT.CvePS = D.CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, @Fecha) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, @Fecha) + ) + ORDER BY + ISNULL(CT.FechaInicioVigencia, CONVERT(date, '19000101')) DESC, + CT.CveCostoTramite DESC + ) AS Costo + INNER JOIN dbo.Gral_SAT_ObjetoImpuesto AS OI + ON OI.CveObjetoImpuesto = Costo.CveObjetoImpuesto + WHERE D.CveTramitePago = @CveTramitePago + AND OI.CodigoObjetoImpuesto = '02' + AND NOT EXISTS + ( + SELECT 1 + FROM dbo.Tra_CostosTramiteImpuestos AS CTI + WHERE CTI.CveCostoTramite = Costo.CveCostoTramite + ) + ) + BEGIN + SELECT + @ErrorMostrar = 'Uno o más costos son objeto de impuesto y no tienen impuestos configurados.', + @ErrorTipo = 2; + RETURN; + END; + + IF EXISTS + ( + SELECT 1 + FROM dbo.Tra_SolicitudesPagos WITH (UPDLOCK, HOLDLOCK) + WHERE CveSolicitud = @CveSolicitud + AND CveTramitePago = @CveTramitePago + ) + BEGIN + SELECT + @ErrorMostrar = 'El pago seleccionado ya fue realizado.', + @ErrorTipo = 2; + RETURN; + END; + + SELECT TOP (1) + @CveSubProyecto = CveSubProyecto + FROM dbo.SubProyectos + WHERE Estatus = 1 + ORDER BY + CASE WHEN Serie = 'TRA' THEN 0 ELSE 1 END, + CveSubProyecto; + + SELECT TOP (1) + @CveEstatusFactura = CveEstatusFactura + FROM dbo.EstatusFactura + WHERE TipoDocto = 'Rem' + AND Estado = 1 + ORDER BY CveEstatusFactura; + + IF @CveSubProyecto IS NULL OR @CveEstatusFactura IS NULL + BEGIN + SELECT + @ErrorMostrar = 'La configuración mínima de ventas no está disponible; favor de contactar a Sistemas.', + @ErrorTipo = 2; + RETURN; + END; + + SELECT + @Folio = ISNULL(MAX(Folio), 0) + 1 + FROM dbo.Ventas WITH (UPDLOCK, HOLDLOCK) + WHERE CveSubProyecto = @CveSubProyecto + AND TipoDocto = 'Rem' + AND Serie = 'TRA'; + + INSERT dbo.Ventas + ( + TipoDocto, + Serie, + Folio, + Fecha, + CveCliente, + CveVendedor, + CveMetodoPago, + CondicionesPago, + FormaPago, + SubTotal, + Descuento, + IVA, + IEPS, + RetIVA, + RetISR, + Total, + Observaciones, + CveEstatusFactura, + FechaEjecucion, + CveSubProyecto, + CveUsuario, + CveFormaPago33, + CveMetodoPago33, + FechaIng, + FechaVencimiento2, + Ref, + DescripcionVenta, + FechaParaSurtir + ) + VALUES + ( + 'Rem', + 'TRA', + @Folio, + @Fecha, + @CveCliente, + @CveVendedor, + 2, + 0, + 99, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ISNULL(@Observaciones, ''), + @CveEstatusFactura, + @Fecha, + @CveSubProyecto, + @CveUsuarioEjecuta, + 99, + 2, + @Fecha, + CONVERT(date, @Fecha), + CONVERT(varchar(50), @CveSolicitud), + 'Pago de trámite', + @Fecha + ); + + SET @CveVenta = CONVERT(int, SCOPE_IDENTITY()); + + INSERT dbo.VentasDetalle + ( + CveVenta, + CvePS, + CveSolicitud, + CveCostoTramite, + Cantidad, + PrecioUnitario, + Descuento, + IVA, + Importe, + CveObjetoImpuesto, + EsFondoInicial, + FechaAlta, + CveUsuario + ) + SELECT + @CveVenta, + D.CvePS, + @CveSolicitud, + Costo.CveCostoTramite, + D.Cantidad, + CONVERT(decimal(18, 2), Costo.Importe), + CONVERT(decimal(18, 2), 0), + CONVERT(decimal(18, 2), 0), + CONVERT( + decimal(18, 2), + ROUND(D.Cantidad * Costo.Importe, 2)), + Costo.CveObjetoImpuesto, + 0, + @Fecha, + @CveUsuarioEjecuta + FROM dbo.Tra_PagosDetalle AS D + CROSS APPLY + ( + SELECT TOP (1) + CT.CveCostoTramite, + CT.Importe, + CT.CveObjetoImpuesto + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = @CveTipoTramite + AND CT.CvePS = D.CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, @Fecha) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, @Fecha) + ) + ORDER BY + ISNULL(CT.FechaInicioVigencia, CONVERT(date, '19000101')) DESC, + CT.CveCostoTramite DESC + ) AS Costo + WHERE D.CveTramitePago = @CveTramitePago; + + INSERT dbo.VentasDetalleImpuestos + ( + CveVentaDetalle, + EsRetencion, + Base, + Impuesto, + TipoFactor, + TasaOCuota, + Importe, + FechaAlta, + CveUsuario + ) + SELECT + VD.CveVentaDetalle, + CTI.EsRetencion, + CONVERT(decimal(19, 6), VD.Importe - VD.Descuento), + CTI.Impuesto, + CTI.TipoFactor, + CTI.TasaOCuota, + CONVERT( + decimal(18, 2), + CASE + WHEN CTI.TipoFactor = 'Exento' THEN 0 + WHEN CTI.TipoFactor = 'Cuota' + THEN ROUND(VD.Cantidad * CTI.TasaOCuota, 2) + ELSE ROUND( + (VD.Importe - VD.Descuento) * CTI.TasaOCuota, + 2) + END), + @Fecha, + @CveUsuarioEjecuta + FROM dbo.VentasDetalle AS VD + INNER JOIN dbo.Tra_CostosTramiteImpuestos AS CTI + ON CTI.CveCostoTramite = VD.CveCostoTramite + WHERE VD.CveVenta = @CveVenta; + + UPDATE VD + SET IVA = ISNULL(ImpuestoIVA.Importe, 0) + FROM dbo.VentasDetalle AS VD + OUTER APPLY + ( + SELECT SUM(VDI.Importe) AS Importe + FROM dbo.VentasDetalleImpuestos AS VDI + WHERE VDI.CveVentaDetalle = VD.CveVentaDetalle + AND VDI.EsRetencion = 0 + AND VDI.Impuesto = '002' + ) AS ImpuestoIVA + WHERE VD.CveVenta = @CveVenta; + + EXEC dbo.PA_Vta_TramiteVentaTotalesUpdate + @CveVenta = @CveVenta, + @CveUsuarioEjecuta = @CveUsuarioEjecuta, + @ErrorMostrar = @ErrorMostrar OUTPUT, + @ErrorTipo = @ErrorTipo OUTPUT, + @Return = @Return OUTPUT; + + IF @Return = 0 + RETURN; + + INSERT dbo.Tra_SolicitudesPagos + ( + CveSolicitud, + CveTramitePago, + CveVenta, + FechaAlta, + CveUsuario + ) + VALUES + ( + @CveSolicitud, + @CveTramitePago, + @CveVenta, + @Fecha, + @CveUsuarioEjecuta + ); + + SELECT @CveVenta AS CveVenta; + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al registrar la venta del trámite; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Vta_TramiteVentaSave'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Vta_TramiteConceptoDelete + @CveSolicitud int, + @CveTramitePago int, + @CveVentaDetalle 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 + DECLARE @CveVenta int; + + SELECT @CveVenta = V.CveVenta + FROM dbo.Tra_SolicitudesPagos AS SP + INNER JOIN dbo.Ventas AS V + ON V.CveVenta = SP.CveVenta + WHERE SP.CveSolicitud = @CveSolicitud + AND SP.CveTramitePago = @CveTramitePago; + + IF @CveVenta IS NULL + BEGIN + SELECT + @ErrorMostrar = 'Primero debe generar la venta del trámite.', + @ErrorTipo = 2; + RETURN; + END; + + IF EXISTS + ( + SELECT 1 + FROM dbo.Ventas + WHERE CveVenta = @CveVenta + AND SeTimbro = 1 + ) + BEGIN + SELECT + @ErrorMostrar = 'No se pueden modificar los conceptos de una venta timbrada.', + @ErrorTipo = 2; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.VentasDetalle + WHERE CveVentaDetalle = @CveVentaDetalle + AND CveVenta = @CveVenta + ) + BEGIN + SELECT + @ErrorMostrar = 'El concepto seleccionado no pertenece a la venta.', + @ErrorTipo = 2; + RETURN; + END; + + IF + ( + SELECT COUNT(*) + FROM dbo.VentasDetalle + WHERE CveVenta = @CveVenta + ) <= 1 + BEGIN + SELECT + @ErrorMostrar = 'La venta debe conservar al menos un concepto.', + @ErrorTipo = 2; + RETURN; + END; + + DELETE dbo.VentasDetalleImpuestos + WHERE CveVentaDetalle = @CveVentaDetalle; + + DELETE dbo.VentasDetalle + WHERE CveVentaDetalle = @CveVentaDetalle + AND CveVenta = @CveVenta; + + EXEC dbo.PA_Vta_TramiteVentaTotalesUpdate + @CveVenta = @CveVenta, + @CveUsuarioEjecuta = @CveUsuarioEjecuta, + @ErrorMostrar = @ErrorMostrar OUTPUT, + @ErrorTipo = @ErrorTipo OUTPUT, + @Return = @Return OUTPUT; + + IF @Return = 0 + RETURN; + + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al eliminar el concepto de la venta; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Vta_TramiteConceptoDelete'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Vta_TramiteConceptoSave + @CveSolicitud int, + @CveTramitePago int, + @CveVentaDetalle int = NULL, + @CvePS int, + @Cantidad decimal(18, 4), + @PrecioUnitario decimal(18, 2), + @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 + @CveVenta int, + @CveTipoTramite int, + @CveCostoTramite int, + @CveObjetoImpuesto tinyint, + @Descuento decimal(18, 2) = 0, + @IVA decimal(18, 2), + @Importe decimal(18, 2), + @Fecha datetime = GETDATE(); + + SELECT + @CveVenta = V.CveVenta, + @CveTipoTramite = S.CveTipoTramite + FROM dbo.Tra_SolicitudesPagos AS SP + INNER JOIN dbo.Ventas AS V + ON V.CveVenta = SP.CveVenta + INNER JOIN dbo.Tra_Solicitudes AS S + ON S.CveSolicitud = SP.CveSolicitud + WHERE SP.CveSolicitud = @CveSolicitud + AND SP.CveTramitePago = @CveTramitePago; + + IF @CveVenta IS NULL + BEGIN + SELECT + @ErrorMostrar = 'Primero debe generar la venta del trámite.', + @ErrorTipo = 2; + RETURN; + END; + + IF EXISTS + ( + SELECT 1 + FROM dbo.Ventas + WHERE CveVenta = @CveVenta + AND SeTimbro = 1 + ) + BEGIN + SELECT + @ErrorMostrar = 'No se pueden modificar los conceptos de una venta timbrada.', + @ErrorTipo = 2; + RETURN; + END; + + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.ProductosServicios + WHERE CvePS = @CvePS + AND Estado = 1 + ) + OR @Cantidad <= 0 + OR @PrecioUnitario < 0 + BEGIN + SELECT + @ErrorMostrar = 'Capture un producto activo, cantidad y precio unitario válidos.', + @ErrorTipo = 2; + RETURN; + END; + + SELECT TOP (1) + @CveCostoTramite = CT.CveCostoTramite, + @CveObjetoImpuesto = CT.CveObjetoImpuesto + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = @CveTipoTramite + AND CT.CvePS = @CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, @Fecha) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, @Fecha) + ) + ORDER BY + ISNULL(CT.FechaInicioVigencia, CONVERT(date, '19000101')) DESC, + CT.CveCostoTramite DESC; + + IF @CveCostoTramite IS NULL + BEGIN + SELECT + @ErrorMostrar = 'El producto seleccionado no tiene un costo vigente para este trámite.', + @ErrorTipo = 2; + RETURN; + END; + + IF EXISTS + ( + SELECT 1 + FROM dbo.Gral_SAT_ObjetoImpuesto + WHERE CveObjetoImpuesto = @CveObjetoImpuesto + AND CodigoObjetoImpuesto = '02' + ) + AND NOT EXISTS + ( + SELECT 1 + FROM dbo.Tra_CostosTramiteImpuestos + WHERE CveCostoTramite = @CveCostoTramite + ) + BEGIN + SELECT + @ErrorMostrar = 'El costo es objeto de impuesto y no tiene impuestos configurados.', + @ErrorTipo = 2; + RETURN; + END; + + IF @CveVentaDetalle IS NOT NULL + BEGIN + SELECT @Descuento = Descuento + FROM dbo.VentasDetalle + WHERE CveVentaDetalle = @CveVentaDetalle + AND CveVenta = @CveVenta; + END; + + SET @Importe = CONVERT( + decimal(18, 2), + ROUND(@Cantidad * @PrecioUnitario, 2)); + + IF @Descuento > @Importe + BEGIN + SELECT + @ErrorMostrar = 'El descuento del concepto no puede ser mayor que su importe.', + @ErrorTipo = 2; + RETURN; + END; + + IF @CveVentaDetalle IS NULL + BEGIN + INSERT dbo.VentasDetalle + ( + CveVenta, + CvePS, + CveSolicitud, + CveCostoTramite, + Cantidad, + PrecioUnitario, + Descuento, + IVA, + Importe, + CveObjetoImpuesto, + EsFondoInicial, + FechaAlta, + CveUsuario + ) + VALUES + ( + @CveVenta, + @CvePS, + @CveSolicitud, + @CveCostoTramite, + @Cantidad, + @PrecioUnitario, + 0, + 0, + @Importe, + @CveObjetoImpuesto, + 0, + @Fecha, + @CveUsuarioEjecuta + ); + + SET @CveVentaDetalle = CONVERT(int, SCOPE_IDENTITY()); + END + ELSE + BEGIN + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.VentasDetalle + WHERE CveVentaDetalle = @CveVentaDetalle + AND CveVenta = @CveVenta + ) + BEGIN + SELECT + @ErrorMostrar = 'El concepto seleccionado no pertenece a la venta.', + @ErrorTipo = 2; + RETURN; + END; + + DELETE dbo.VentasDetalleImpuestos + WHERE CveVentaDetalle = @CveVentaDetalle; + + UPDATE dbo.VentasDetalle + SET CvePS = @CvePS, + CveCostoTramite = @CveCostoTramite, + Cantidad = @Cantidad, + PrecioUnitario = @PrecioUnitario, + IVA = 0, + Importe = @Importe, + CveObjetoImpuesto = @CveObjetoImpuesto, + CveUsuario = @CveUsuarioEjecuta + WHERE CveVentaDetalle = @CveVentaDetalle; + END; + + INSERT dbo.VentasDetalleImpuestos + ( + CveVentaDetalle, + EsRetencion, + Base, + Impuesto, + TipoFactor, + TasaOCuota, + Importe, + FechaAlta, + CveUsuario + ) + SELECT + @CveVentaDetalle, + CTI.EsRetencion, + CONVERT(decimal(19, 6), @Importe - @Descuento), + CTI.Impuesto, + CTI.TipoFactor, + CTI.TasaOCuota, + CONVERT( + decimal(18, 2), + CASE + WHEN CTI.TipoFactor = 'Exento' THEN 0 + WHEN CTI.TipoFactor = 'Cuota' + THEN ROUND(@Cantidad * CTI.TasaOCuota, 2) + ELSE ROUND( + (@Importe - @Descuento) * CTI.TasaOCuota, + 2) + END), + @Fecha, + @CveUsuarioEjecuta + FROM dbo.Tra_CostosTramiteImpuestos AS CTI + WHERE CTI.CveCostoTramite = @CveCostoTramite; + + SELECT + @IVA = ISNULL(SUM(VDI.Importe), 0) + FROM dbo.VentasDetalleImpuestos AS VDI + WHERE VDI.CveVentaDetalle = @CveVentaDetalle + AND VDI.EsRetencion = 0 + AND VDI.Impuesto = '002'; + + UPDATE dbo.VentasDetalle + SET IVA = @IVA + WHERE CveVentaDetalle = @CveVentaDetalle; + + EXEC dbo.PA_Vta_TramiteVentaTotalesUpdate + @CveVenta = @CveVenta, + @CveUsuarioEjecuta = @CveUsuarioEjecuta, + @ErrorMostrar = @ErrorMostrar OUTPUT, + @ErrorTipo = @ErrorTipo OUTPUT, + @Return = @Return OUTPUT; + + IF @Return = 0 + RETURN; + + SELECT @CveVentaDetalle AS CveVentaDetalle; + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al guardar el concepto de la venta; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Vta_TramiteConceptoSave'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO + +CREATE OR ALTER PROCEDURE dbo.PA_Vta_TramiteVentaDatosCfdiGet + @CveSolicitud int, + @CveTramitePago 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 + DECLARE @CveVenta int; + + SELECT @CveVenta = V.CveVenta + FROM dbo.Tra_SolicitudesPagos AS SP + INNER JOIN dbo.Ventas AS V + ON V.CveVenta = SP.CveVenta + WHERE SP.CveSolicitud = @CveSolicitud + AND SP.CveTramitePago = @CveTramitePago; + + IF @CveVenta IS NULL + BEGIN + SELECT + @ErrorMostrar = 'Primero debe generar la venta del trámite.', + @ErrorTipo = 2; + RETURN; + END; + + IF EXISTS + ( + SELECT 1 + FROM dbo.Ventas AS V + WHERE V.CveVenta = @CveVenta + AND + ( + V.SeTimbro = 1 + OR EXISTS + ( + SELECT 1 + FROM dbo.Fac_CfdiDocumentos AS D + WHERE D.TipoOrigen = 'VENTA' + AND D.CveOrigen = V.CveVenta + AND D.Estado = 'TIMBRADO' + AND D.CveCfdiTimbradoVigente IS NOT NULL + ) + ) + ) + BEGIN + SELECT + @ErrorMostrar = 'La venta ya se encuentra timbrada.', + @ErrorTipo = 2; + RETURN; + END; + + IF EXISTS + ( + SELECT 1 + FROM dbo.Fac_CfdiDocumentos + WHERE TipoOrigen = 'VENTA' + AND CveOrigen = @CveVenta + AND Estado = 'REVISAR' + ) + BEGIN + SELECT + @ErrorMostrar = 'Existe un intento de timbrado con resultado indeterminado; debe revisarse antes de volver a timbrar.', + @ErrorTipo = 2; + RETURN; + END; + + IF EXISTS + ( + SELECT 1 + FROM dbo.VentasDetalle AS VD + INNER JOIN dbo.Gral_SAT_ObjetoImpuesto AS OI + ON OI.CveObjetoImpuesto = VD.CveObjetoImpuesto + WHERE VD.CveVenta = @CveVenta + AND OI.CodigoObjetoImpuesto = '02' + AND NOT EXISTS + ( + SELECT 1 + FROM dbo.VentasDetalleImpuestos AS VDI + WHERE VDI.CveVentaDetalle = VD.CveVentaDetalle + ) + ) + BEGIN + SELECT + @ErrorMostrar = 'Uno o más conceptos son objeto de impuesto y no tienen impuestos registrados.', + @ErrorTipo = 2; + RETURN; + END; + + SELECT + V.CveVenta, + Borrador.CveCfdiDocumento, + V.CveCliente, + V.Serie, + V.Folio, + V.Fecha, + FP.ClaveFormaPagoSAT AS FormaPago, + MP.ClaveMetodoPagoSAT AS MetodoPago, + M.AbreviaturaSAT AS Moneda, + CONVERT(decimal(19, 6), V.TipoCambio) AS TipoCambio + FROM dbo.Ventas AS V + LEFT JOIN dbo.Vta_FormaPago AS FP + ON FP.CveFormaPago = V.FormaPago + LEFT JOIN dbo.Vta_MetodoPago AS MP + ON MP.CveMetodoPago = V.CveMetodoPago + INNER JOIN dbo.Gral_Monedas AS M + ON M.CveMoneda = V.CveMoneda + OUTER APPLY + ( + SELECT TOP (1) D.CveCfdiDocumento + FROM dbo.Fac_CfdiDocumentos AS D + WHERE D.TipoOrigen = 'VENTA' + AND D.CveOrigen = V.CveVenta + AND D.CveCfdiTimbradoVigente IS NULL + AND D.Estado IN ('BORRADOR', 'LISTO', 'ERROR') + ORDER BY D.CveCfdiDocumento DESC + ) AS Borrador + WHERE V.CveVenta = @CveVenta; + + SELECT + VD.CveVentaDetalle, + SATPS.SATClavePS AS ClaveProductoServicio, + PS.Codigo AS NumeroIdentificacion, + VD.Cantidad, + SATUM.SATClaveUnidadMedida AS ClaveUnidad, + UM.UnidadMedida AS Unidad, + PS.PS AS Descripcion, + VD.PrecioUnitario AS ValorUnitario, + VD.PrecioUnitario AS PU, + VD.Importe AS Total, + VD.Descuento, + OI.CodigoObjetoImpuesto AS ObjetoImpuesto + FROM dbo.VentasDetalle AS VD + INNER JOIN dbo.ProductosServicios AS PS + ON PS.CvePS = VD.CvePS + INNER JOIN dbo.UnidadesMedida AS UM + ON UM.CveUnidadMedida = PS.CveUnidadMedida + LEFT JOIN dbo.Gral_SAT_ProductosServicios AS SATPS + ON SATPS.CveSATPS = PS.CveSATPS + LEFT JOIN dbo.Gral_SAT_UnidadesMedida AS SATUM + ON SATUM.CveSATUnidadMedida = UM.CveSATUnidadMedida + INNER JOIN dbo.Gral_SAT_ObjetoImpuesto AS OI + ON OI.CveObjetoImpuesto = VD.CveObjetoImpuesto + WHERE VD.CveVenta = @CveVenta + ORDER BY VD.CveVentaDetalle; + + SELECT + VDI.CveVentaDetalle, + VDI.EsRetencion, + VDI.Base, + VDI.Impuesto, + VDI.TipoFactor, + VDI.TasaOCuota, + VDI.Importe + FROM dbo.VentasDetalleImpuestos AS VDI + INNER JOIN dbo.VentasDetalle AS VD + ON VD.CveVentaDetalle = VDI.CveVentaDetalle + WHERE VD.CveVenta = @CveVenta + ORDER BY + VDI.CveVentaDetalle, + VDI.EsRetencion, + VDI.Impuesto, + VDI.CveVentaDetalleImpuesto; + + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al preparar los datos fiscales de la venta; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE(ERROR_PROCEDURE(), 'PA_Vta_TramiteVentaDatosCfdiGet'), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE()); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO diff --git a/PA_Vta_FraccionamientoPagoPendienteList_Actualizar_20260901.sql b/PA_Vta_FraccionamientoPagoPendienteList_Actualizar_20260901.sql new file mode 100644 index 0000000..3e5ffbe --- /dev/null +++ b/PA_Vta_FraccionamientoPagoPendienteList_Actualizar_20260901.sql @@ -0,0 +1,185 @@ +CREATE OR ALTER PROCEDURE dbo.PA_Vta_FraccionamientoPagoPendienteList + @CveFraccionamiento 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 + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.Fraccionamientos + WHERE CveFraccionamiento = @CveFraccionamiento + ) + BEGIN + SELECT + @ErrorMostrar = 'El fraccionamiento solicitado no existe.', + @ErrorTipo = 2; + RETURN; + END; + + ;WITH Conceptos AS + ( + SELECT + S.CveSolicitud, + P.CveTramitePago, + COUNT_BIG(*) AS NumeroConceptos, + SUM( + ROUND( + CONVERT(decimal(18, 4), Costo.Importe), + 2 + ) * D.Cantidad + ) AS SubTotal, + SUM( + ROUND( + CONVERT(decimal(18, 4), Costo.Importe) + * D.Cantidad + * ISNULL(ImpuestoIVA.TasaOCuota, 0), + 2 + ) + ) AS IVA + FROM dbo.Tra_Solicitudes AS S + INNER JOIN dbo.Tra_Pagos AS P + ON P.CveTipoTramite = S.CveTipoTramite + AND P.EsActivo = 1 + INNER JOIN dbo.Tra_PagosDetalle AS D + ON D.CveTramitePago = P.CveTramitePago + INNER JOIN dbo.ProductosServicios AS PS + ON PS.CvePS = D.CvePS + OUTER APPLY + ( + SELECT TOP (1) + CT.CveCostoTramite, + CT.Importe + FROM dbo.Tra_CostosTramite AS CT + WHERE CT.CveTipoTramite = S.CveTipoTramite + AND CT.CvePS = D.CvePS + AND CT.EsActivo = 1 + AND + ( + CT.FechaInicioVigencia IS NULL + OR CT.FechaInicioVigencia <= CONVERT(date, GETDATE()) + ) + AND + ( + CT.FechaFinVigencia IS NULL + OR CT.FechaFinVigencia >= CONVERT(date, GETDATE()) + ) + ORDER BY + ISNULL( + CT.FechaInicioVigencia, + CONVERT(date, '19000101')) DESC, + CT.CveCostoTramite DESC + ) AS Costo + OUTER APPLY + ( + SELECT TOP (1) CTI.TasaOCuota + FROM dbo.Tra_CostosTramiteImpuestos AS CTI + WHERE CTI.CveCostoTramite = Costo.CveCostoTramite + AND CTI.EsRetencion = 0 + AND CTI.Impuesto = '002' + AND CTI.TipoFactor = 'Tasa' + ) AS ImpuestoIVA + WHERE S.CveFraccionamiento = @CveFraccionamiento + GROUP BY + S.CveSolicitud, + P.CveTramitePago + ) + SELECT + S.CveSolicitud, + P.CveTramitePago, + SP.CveVenta, + CONVERT( + bit, + CASE + WHEN SP.CveSolicitudPago IS NOT NULL + AND V.CveVenta IS NOT NULL THEN 1 + ELSE 0 + END + ) AS VentaGenerada, + S.Folio AS FolioSolicitud, + E.Etapa, + TT.TipoTramite, + P.Nombre AS NombrePago, + CONVERT(int, C.NumeroConceptos) AS NumeroConceptos, + CONVERT( + decimal(18, 2), + CASE + WHEN V.CveVenta IS NULL THEN C.SubTotal + ELSE V.SubTotal + END + ) AS SubTotal, + CONVERT( + decimal(18, 2), + CASE + WHEN V.CveVenta IS NULL THEN C.IVA + ELSE V.IVA + END + ) AS IVA, + CONVERT( + decimal(18, 2), + CASE + WHEN V.CveVenta IS NULL THEN C.SubTotal + C.IVA + ELSE V.Total + END + ) AS Total + FROM Conceptos AS C + INNER JOIN dbo.Tra_Solicitudes AS S + ON S.CveSolicitud = C.CveSolicitud + INNER JOIN dbo.FraccionamientosEtapas AS E + ON E.CveFraccionamientoEtapa = S.CveFraccionamientoEtapa + INNER JOIN dbo.Tra_TiposTramite AS TT + ON TT.CveTipoTramite = S.CveTipoTramite + INNER JOIN dbo.Tra_Pagos AS P + ON P.CveTramitePago = C.CveTramitePago + LEFT JOIN dbo.Tra_SolicitudesPagos AS SP + ON SP.CveSolicitud = S.CveSolicitud + AND SP.CveTramitePago = P.CveTramitePago + LEFT JOIN dbo.Ventas AS V + ON V.CveVenta = SP.CveVenta + WHERE NOT EXISTS + ( + SELECT 1 + FROM dbo.Vta_Pagos AS VP + WHERE VP.CveSolicitud = S.CveSolicitud + AND VP.CveTramitePago = P.CveTramitePago + AND VP.CveVenta = SP.CveVenta + AND VP.EsActivo = 1 + ) + ORDER BY + E.Orden, + S.CveSolicitud, + P.Orden, + P.CveTramitePago; + + SET @Return = 1; + END TRY + BEGIN CATCH + SELECT + @ErrorMostrar = 'Ocurrió un error al consultar los pagos pendientes; favor de contactar a Sistemas.', + @ErrorTipo = 1, + @Return = 0; + + DECLARE @ErrorTecnico nvarchar(2048) = CONCAT( + 'Número: ', ERROR_NUMBER(), + '; Procedimiento: ', COALESCE( + ERROR_PROCEDURE(), + 'PA_Vta_FraccionamientoPagoPendienteList' + ), + '; Línea: ', ERROR_LINE(), + '; Detalle: ', ERROR_MESSAGE() + ); + + RAISERROR(N'%s', 10, 1, @ErrorTecnico); + END CATCH; +END; +GO diff --git a/SIG.Web/App_Code/Handlers/Facturacion/DescargaXmlCfdi.cs b/SIG.Web/App_Code/Handlers/Facturacion/DescargaXmlCfdi.cs new file mode 100644 index 0000000..2dac8f9 --- /dev/null +++ b/SIG.Web/App_Code/Handlers/Facturacion/DescargaXmlCfdi.cs @@ -0,0 +1,144 @@ +using System; +using System.Globalization; +using System.IO; +using System.Web; +using System.Web.SessionState; +using SIG.Web.AppCode.Configuration; +using SIG.Web.AppCode.Models.Common; +using SIG.Web.AppCode.Models.Facturacion; +using SIG.Web.AppCode.Session; + +namespace SIG.Web.Pages.Facturacion +{ + public sealed class DescargaXmlCfdi + : IHttpHandler, IRequiresSessionState + { + public bool IsReusable + { + get { return false; } + } + + public void ProcessRequest(HttpContext contexto) + { + UserSessionModel usuarioActual = ObtenerUsuarioActual(); + + if (usuarioActual == null) + { + EscribirError( + contexto, + 401, + "Su sesión no está disponible. Inicie sesión nuevamente."); + return; + } + + long cveCfdiDocumento; + + if (!IntentarLeerEnteroLargo( + contexto.Request.QueryString["documento"], + out cveCfdiDocumento)) + { + EscribirError( + contexto, + 400, + "El documento CFDI solicitado no es válido."); + return; + } + + DbResult resultado = + AppServices.ServicioTimbradoCfdi.ObtenerXml( + cveCfdiDocumento, + false, + usuarioActual.CveUsuario, + usuarioActual.NombreDominio); + + if (!resultado.Success || + resultado.Data == null || + resultado.Data.Contenido == null || + resultado.Data.Contenido.Length == 0) + { + EscribirError( + contexto, + 404, + resultado == null || + string.IsNullOrWhiteSpace(resultado.UserMessage) + ? "No se encontró el XML sin timbrar." + : resultado.UserMessage); + return; + } + + ModeloArchivoCfdi archivo = resultado.Data; + byte[] contenido = archivo.Contenido; + + contexto.Response.Clear(); + contexto.Response.ClearHeaders(); + contexto.Response.TrySkipIisCustomErrors = true; + contexto.Response.Cache.SetCacheability(HttpCacheability.NoCache); + contexto.Response.Cache.SetNoStore(); + contexto.Response.ContentType = + string.IsNullOrWhiteSpace(archivo.TipoContenido) + ? "application/xml" + : archivo.TipoContenido; + contexto.Response.AddHeader( + "Content-Disposition", + "attachment; filename=\"" + + SanitizarNombreArchivo(archivo.NombreArchivo) + + "\""); + contexto.Response.AddHeader( + "Content-Length", + contenido.Length.ToString(CultureInfo.InvariantCulture)); + contexto.Response.OutputStream.Write( + contenido, + 0, + contenido.Length); + contexto.ApplicationInstance.CompleteRequest(); + } + + private static UserSessionModel ObtenerUsuarioActual() + { + return AppServices.UserContext != null && + AppServices.UserContext.IsAuthenticated() + ? AppServices.UserContext.GetCurrentUser() + : null; + } + + private static bool IntentarLeerEnteroLargo( + string valor, + out long resultado) + { + return long.TryParse( + valor, + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out resultado) && + resultado > 0; + } + + private static string SanitizarNombreArchivo(string nombreArchivo) + { + string nombre = Path.GetFileName( + string.IsNullOrWhiteSpace(nombreArchivo) + ? "CFDI-SinTimbrar.xml" + : nombreArchivo); + + return nombre + .Replace("\"", string.Empty) + .Replace("\r", string.Empty) + .Replace("\n", string.Empty); + } + + private static void EscribirError( + HttpContext contexto, + int codigoEstado, + string mensaje) + { + contexto.Response.Clear(); + contexto.Response.TrySkipIisCustomErrors = true; + contexto.Response.StatusCode = codigoEstado; + contexto.Response.ContentType = "text/plain"; + contexto.Response.ContentEncoding = + System.Text.Encoding.UTF8; + contexto.Response.Write(mensaje); + contexto.ApplicationInstance.CompleteRequest(); + } + } +} diff --git a/SIG.Web/App_Code/Models/General/ModelosProductoServicio.cs b/SIG.Web/App_Code/Models/General/ModelosProductoServicio.cs index 45037dd..eab9d9c 100644 --- a/SIG.Web/App_Code/Models/General/ModelosProductoServicio.cs +++ b/SIG.Web/App_Code/Models/General/ModelosProductoServicio.cs @@ -15,6 +15,11 @@ namespace SIG.Web.AppCode.Models.General public int CveUnidadMedida { get; set; } public string UnidadMedida { get; set; } public decimal Precio { get; set; } + public byte CveObjetoImpuesto { get; set; } + public string ObjetoImpuesto { get; set; } + public string CodigoObjetoImpuesto { get; set; } + public decimal? IVA { get; set; } + public string TratamientoIVA { get; set; } public bool EsActivo { get; set; } } @@ -24,6 +29,7 @@ namespace SIG.Web.AppCode.Models.General { Clasificaciones = new List(); UnidadesMedida = new List(); + ObjetosImpuesto = new List(); } public List Clasificaciones @@ -37,6 +43,12 @@ namespace SIG.Web.AppCode.Models.General get; set; } + + public List ObjetosImpuesto + { + get; + set; + } } public sealed class ModeloOpcionClasificacionProducto @@ -50,4 +62,10 @@ namespace SIG.Web.AppCode.Models.General public int CveUnidadMedida { get; set; } public string UnidadMedida { get; set; } } + + public sealed class ModeloOpcionObjetoImpuesto + { + public byte CveObjetoImpuesto { get; set; } + public string ObjetoImpuesto { get; set; } + } } diff --git a/SIG.Web/App_Code/Models/Tramites/TramiteCatalogModels.cs b/SIG.Web/App_Code/Models/Tramites/TramiteCatalogModels.cs index f331eaa..55dbaf9 100644 --- a/SIG.Web/App_Code/Models/Tramites/TramiteCatalogModels.cs +++ b/SIG.Web/App_Code/Models/Tramites/TramiteCatalogModels.cs @@ -130,7 +130,15 @@ namespace SIG.Web.AppCode.Models.Tramites public string UnidadMedida { get; set; } public string Abreviatura { get; set; } public decimal Cantidad { get; set; } - public decimal Precio { get; set; } + public decimal PrecioProducto { get; set; } + public decimal Importe { get; set; } + public byte CveObjetoImpuesto { get; set; } + public string CodigoObjetoImpuesto { get; set; } + public string ObjetoImpuesto { get; set; } + public decimal PorcentajeIVA { get; set; } + public decimal ImporteIVA { get; set; } + public DateTime? FechaInicioVigencia { get; set; } + public DateTime? FechaFinVigencia { get; set; } public decimal Total { get; set; } public string UnidadMedidaTexto @@ -154,6 +162,9 @@ namespace SIG.Web.AppCode.Models.Tramites public string UnidadMedida { get; set; } public string Abreviatura { get; set; } public decimal Precio { get; set; } + public byte CveObjetoImpuesto { get; set; } + public string CodigoObjetoImpuesto { get; set; } + public decimal PorcentajeIVA { get; set; } public bool EsActivo { get; set; } public string ProductoDisplay diff --git a/SIG.Web/App_Code/Models/Ventas/ModelosVentaTramite.cs b/SIG.Web/App_Code/Models/Ventas/ModelosVentaTramite.cs index 30df634..de89513 100644 --- a/SIG.Web/App_Code/Models/Ventas/ModelosVentaTramite.cs +++ b/SIG.Web/App_Code/Models/Ventas/ModelosVentaTramite.cs @@ -129,6 +129,12 @@ namespace SIG.Web.AppCode.Models.Ventas public sealed class ModeloConceptoCfdiVentaTramite { + public ModeloConceptoCfdiVentaTramite() + { + Impuestos = new List(); + } + + public int CveVentaDetalle { get; set; } public string ClaveProductoServicio { get; set; } public string NumeroIdentificacion { get; set; } public decimal Cantidad { get; set; } @@ -141,6 +147,18 @@ namespace SIG.Web.AppCode.Models.Ventas public decimal BaseIVA { get; set; } public decimal TasaIVA { get; set; } public decimal ImporteIVA { get; set; } + public List Impuestos { get; set; } + } + + public sealed class ModeloImpuestoCfdiVentaTramite + { + public int CveVentaDetalle { get; set; } + public bool EsRetencion { get; set; } + public decimal Base { get; set; } + public string Impuesto { get; set; } + public string TipoFactor { get; set; } + public decimal? TasaOCuota { get; set; } + public decimal Importe { get; set; } } public sealed class ModeloTimbradoVentaTramite diff --git a/SIG.Web/App_Code/Repositories/General/RepositorioProductoServicio.cs b/SIG.Web/App_Code/Repositories/General/RepositorioProductoServicio.cs index 448f7f4..db165db 100644 --- a/SIG.Web/App_Code/Repositories/General/RepositorioProductoServicio.cs +++ b/SIG.Web/App_Code/Repositories/General/RepositorioProductoServicio.cs @@ -87,6 +87,12 @@ namespace SIG.Web.AppCode.Repositories.General lector.ToList(); } + if (lector.NextResult()) + { + opciones.ObjetosImpuesto = + lector.ToList(); + } + return opciones; }); } @@ -135,6 +141,16 @@ namespace SIG.Web.AppCode.Repositories.General "@Precio", productoServicio.Precio, DbType.Decimal); + Agregar( + comando, + "@CveObjetoImpuesto", + productoServicio.CveObjetoImpuesto, + DbType.Byte); + Agregar( + comando, + "@IVA", + productoServicio.IVA, + DbType.Decimal); Agregar( comando, "@EsActivo", diff --git a/SIG.Web/App_Code/Repositories/Tramites/TramiteCatalogRepository.cs b/SIG.Web/App_Code/Repositories/Tramites/TramiteCatalogRepository.cs index b87383f..977dc52 100644 --- a/SIG.Web/App_Code/Repositories/Tramites/TramiteCatalogRepository.cs +++ b/SIG.Web/App_Code/Repositories/Tramites/TramiteCatalogRepository.cs @@ -306,6 +306,18 @@ namespace SIG.Web.AppCode.Repositories.Tramites Add(command, "@CvePS", model.CvePS, DbType.Int32); Add(command, "@CveUM", model.CveUM, DbType.Int32); Add(command, "@Cantidad", model.Cantidad, DbType.Decimal); + Add(command, "@Importe", model.Importe, DbType.Decimal); + Add( + command, + "@CveObjetoImpuesto", + model.CveObjetoImpuesto, + DbType.Byte); + Add( + command, + "@PorcentajeIVA", + model.PorcentajeIVA, + DbType.Decimal); + Add(command, "@FechaInicioVigencia", model.FechaInicioVigencia, DbType.Date); }); } diff --git a/SIG.Web/App_Code/Repositories/Ventas/RepositorioVentaTramite.cs b/SIG.Web/App_Code/Repositories/Ventas/RepositorioVentaTramite.cs index bbb485c..1613510 100644 --- a/SIG.Web/App_Code/Repositories/Ventas/RepositorioVentaTramite.cs +++ b/SIG.Web/App_Code/Repositories/Ventas/RepositorioVentaTramite.cs @@ -361,6 +361,34 @@ namespace SIG.Web.AppCode.Repositories.Ventas lector.ToList(); } + if (datos != null && lector.NextResult()) + { + List impuestos = + lector.ToList(); + var conceptosPorDetalle = + new Dictionary(); + + foreach (ModeloConceptoCfdiVentaTramite concepto + in datos.Conceptos) + { + conceptosPorDetalle[concepto.CveVentaDetalle] = + concepto; + } + + foreach (ModeloImpuestoCfdiVentaTramite impuesto + in impuestos) + { + ModeloConceptoCfdiVentaTramite concepto; + + if (conceptosPorDetalle.TryGetValue( + impuesto.CveVentaDetalle, + out concepto)) + { + concepto.Impuestos.Add(impuesto); + } + } + } + return datos; }); } diff --git a/SIG.Web/App_Code/Services/Facturacion/ConstructorXmlCfdi40Profact.cs b/SIG.Web/App_Code/Services/Facturacion/ConstructorXmlCfdi40Profact.cs index 6d54ad3..a1b01a0 100644 --- a/SIG.Web/App_Code/Services/Facturacion/ConstructorXmlCfdi40Profact.cs +++ b/SIG.Web/App_Code/Services/Facturacion/ConstructorXmlCfdi40Profact.cs @@ -138,7 +138,8 @@ namespace SIG.Web.AppCode.Services.Facturacion if (solicitud.TipoCambio.HasValue) { comprobante.TipoCambio = - solicitud.TipoCambio.Value; + NormalizarTipoCambio( + solicitud.TipoCambio.Value); comprobante.TipoCambioSpecified = true; } @@ -260,7 +261,8 @@ namespace SIG.Web.AppCode.Services.Facturacion if (!EsExento(impuesto.TipoFactor)) { - resultado.Importe = impuesto.Importe; + resultado.Importe = + RedondearMoneda(impuesto.Importe); resultado.ImporteSpecified = true; } @@ -325,7 +327,9 @@ namespace SIG.Web.AppCode.Services.Facturacion var traslado = new ComprobanteImpuestosTraslado { - Base = grupo.Sum(elemento => elemento.Base), + Base = RedondearMoneda( + grupo.Sum( + elemento => elemento.Base)), Impuesto = grupo.Key.Impuesto, TipoFactor = grupo.Key.TipoFactor }; @@ -340,7 +344,9 @@ namespace SIG.Web.AppCode.Services.Facturacion if (!EsExento(grupo.Key.TipoFactor)) { traslado.Importe = - grupo.Sum(elemento => elemento.Importe); + RedondearMoneda( + grupo.Sum( + elemento => elemento.Importe)); traslado.ImporteSpecified = true; } @@ -605,6 +611,17 @@ namespace SIG.Web.AppCode.Services.Facturacion MidpointRounding.AwayFromZero); } + private static decimal NormalizarTipoCambio(decimal tipoCambio) + { + string valorNormalizado = tipoCambio.ToString( + "0.######", + System.Globalization.CultureInfo.InvariantCulture); + + return decimal.Parse( + valorNormalizado, + System.Globalization.CultureInfo.InvariantCulture); + } + private static string TextoOpcional(string valor) { return string.IsNullOrWhiteSpace(valor) diff --git a/SIG.Web/App_Code/Services/General/FabricaBitacoraProductoServicio.cs b/SIG.Web/App_Code/Services/General/FabricaBitacoraProductoServicio.cs index 7785008..8e8d9c1 100644 --- a/SIG.Web/App_Code/Services/General/FabricaBitacoraProductoServicio.cs +++ b/SIG.Web/App_Code/Services/General/FabricaBitacoraProductoServicio.cs @@ -92,6 +92,16 @@ namespace SIG.Web.AppCode.Services.General "Precio", ObtenerValor(anterior, elemento => elemento.Precio), actual.Precio), + CrearCampo( + "CveObjetoImpuesto", + ObtenerValor( + anterior, + elemento => elemento.CveObjetoImpuesto), + actual.CveObjetoImpuesto), + CrearCampo( + "IVA", + ObtenerValor(anterior, elemento => elemento.IVA), + actual.IVA), CrearCampo( "Estado", ObtenerValor(anterior, elemento => elemento.EsActivo), diff --git a/SIG.Web/App_Code/Services/General/ServicioProductoServicio.cs b/SIG.Web/App_Code/Services/General/ServicioProductoServicio.cs index 2d922d0..b3acbd1 100644 --- a/SIG.Web/App_Code/Services/General/ServicioProductoServicio.cs +++ b/SIG.Web/App_Code/Services/General/ServicioProductoServicio.cs @@ -238,6 +238,20 @@ namespace SIG.Web.AppCode.Services.General return Fallar("El precio no puede ser negativo."); } + if (productoServicio.CveObjetoImpuesto <= 0) + { + return Fallar( + "Seleccione el objeto de impuesto del producto o servicio."); + } + + if (productoServicio.IVA.HasValue && + productoServicio.IVA.Value != -1m && + productoServicio.IVA.Value != 0m) + { + return Fallar( + "Seleccione IVA general o sin IVA."); + } + return DbResult.Ok(productoServicio.CvePS); } diff --git a/SIG.Web/App_Code/Services/Tramites/TramiteCatalogBitacoraFactory.cs b/SIG.Web/App_Code/Services/Tramites/TramiteCatalogBitacoraFactory.cs index 346a3c9..e83ca0e 100644 --- a/SIG.Web/App_Code/Services/Tramites/TramiteCatalogBitacoraFactory.cs +++ b/SIG.Web/App_Code/Services/Tramites/TramiteCatalogBitacoraFactory.cs @@ -183,7 +183,15 @@ namespace SIG.Web.AppCode.Services.Tramites Field("CveTramitePago", Value(anterior, item => item.CveTramitePago), actual.CveTramitePago), Field("CvePS", Value(anterior, item => item.CvePS), actual.CvePS), Field("CveUM", Value(anterior, item => item.CveUM), actual.CveUM), - Field("Cantidad", Value(anterior, item => item.Cantidad), actual.Cantidad))); + Field("Cantidad", Value(anterior, item => item.Cantidad), actual.Cantidad), + Field( + "CveObjetoImpuesto", + Value(anterior, item => item.CveObjetoImpuesto), + actual.CveObjetoImpuesto), + Field( + "PorcentajeIVA", + Value(anterior, item => item.PorcentajeIVA), + actual.PorcentajeIVA))); evento.Relaciones.Add(Relation( "Tra_Pagos", diff --git a/SIG.Web/App_Code/Services/Tramites/TramiteCatalogService.cs b/SIG.Web/App_Code/Services/Tramites/TramiteCatalogService.cs index 3329565..b559810 100644 --- a/SIG.Web/App_Code/Services/Tramites/TramiteCatalogService.cs +++ b/SIG.Web/App_Code/Services/Tramites/TramiteCatalogService.cs @@ -329,10 +329,15 @@ namespace SIG.Web.AppCode.Services.Tramites model.CveTramitePago <= 0 || model.CvePS <= 0 || model.CveUM <= 0 || - model.Cantidad <= 0) + model.Cantidad <= 0 || + model.Importe < 0 || + model.CveObjetoImpuesto <= 0 || + model.PorcentajeIVA < 0 || + model.PorcentajeIVA > 1 || + !model.FechaInicioVigencia.HasValue) { return Fail( - "Seleccione el producto o servicio, la unidad de medida y capture una cantidad mayor que cero."); + "Seleccione el producto o servicio, la unidad de medida, el objeto de impuesto y capture una cantidad, un importe, un IVA y una fecha de inicio de vigencia válidos."); } return Save( diff --git a/SIG.Web/Documentos/Clientes/4/CFDI/Ventas/cfdi-venta-1019-documento-1.xml b/SIG.Web/Documentos/Clientes/4/CFDI/Ventas/cfdi-venta-1019-documento-1.xml new file mode 100644 index 0000000..4a38505 --- /dev/null +++ b/SIG.Web/Documentos/Clientes/4/CFDI/Ventas/cfdi-venta-1019-documento-1.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/SIG.Web/Documentos/Clientes/4/CFDI/Ventas/cfdi-venta-1020-documento-2.xml b/SIG.Web/Documentos/Clientes/4/CFDI/Ventas/cfdi-venta-1020-documento-2.xml new file mode 100644 index 0000000..180aa3b --- /dev/null +++ b/SIG.Web/Documentos/Clientes/4/CFDI/Ventas/cfdi-venta-1020-documento-2.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/SIG.Web/Pages/Catalogos/ProductosServicios.aspx b/SIG.Web/Pages/Catalogos/ProductosServicios.aspx index 182de73..2b504cc 100644 --- a/SIG.Web/Pages/Catalogos/ProductosServicios.aspx +++ b/SIG.Web/Pages/Catalogos/ProductosServicios.aspx @@ -65,6 +65,10 @@ HeaderText="Precio" DataFormatString="{0:N2}" ItemStyle-HorizontalAlign="Right" /> + + @@ -276,6 +280,34 @@ + +
+
+
+ + +
+
+
+
+ + + + + +
+
+
+
+ + + + Al seleccionar el producto se propone su precio unitario actual. + Los cambios posteriores generan una nueva vigencia para el trámite. + +
+
+
+
+ + + + + + + +
+
+
+
+ + + + Capture 16 para una tasa del 16%. + +
+
+
-
diff --git a/SIG.Web/Pages/Catalogos/TipoTramiteDetalle.aspx.cs b/SIG.Web/Pages/Catalogos/TipoTramiteDetalle.aspx.cs index 3f7e58f..a8e716a 100644 --- a/SIG.Web/Pages/Catalogos/TipoTramiteDetalle.aspx.cs +++ b/SIG.Web/Pages/Catalogos/TipoTramiteDetalle.aspx.cs @@ -65,12 +65,6 @@ namespace SIG.Web.Pages.Catalogos set { ViewState["CveTramitePagoDetalleActual"] = value; } } - private int CveProductoPrecioActual - { - get { return ViewState["CveProductoPrecioActual"] as int? ?? 0; } - set { ViewState["CveProductoPrecioActual"] = value; } - } - private List ProductosServiciosDisponibles { @@ -700,6 +694,9 @@ namespace SIG.Web.Pages.Catalogos DdlProductService.SelectedIndex = 0; DdlUnidadMedidaDetallePago.SelectedIndex = 0; TxtPaymentDetailQuantity.Text = "1"; + TxtPaymentDetailAmount.Text = string.Empty; + DdlObjetoImpuestoDetallePago.SelectedIndex = 0; + TxtPorcentajeIvaDetallePago.Text = "16.00"; UpPaymentDetailModal.Update(); Show("ModalPaymentDetail"); } @@ -719,10 +716,6 @@ namespace SIG.Web.Pages.Catalogos { EditPaymentDetail(id); } - else if (e.CommandName == "EditarPrecioGlobal") - { - EditGlobalPrice(id); - } else if (e.CommandName == "EliminarDetalle") { DeletePaymentDetail(id); @@ -735,6 +728,9 @@ namespace SIG.Web.Pages.Catalogos int cveProductoServicio; int cveUnidadMedida; decimal cantidad; + decimal importe; + byte cveObjetoImpuesto; + decimal porcentajeIvaCapturado; if (!int.TryParse( DdlProductService.SelectedValue, out cveProductoServicio) || @@ -746,10 +742,23 @@ namespace SIG.Web.Pages.Catalogos !TryParseDecimal( TxtPaymentDetailQuantity.Text, out cantidad) || - cantidad <= 0) + cantidad <= 0 || + !TryParseDecimal( + TxtPaymentDetailAmount.Text, + out importe) || + importe < 0 || + !byte.TryParse( + DdlObjetoImpuestoDetallePago.SelectedValue, + out cveObjetoImpuesto) || + cveObjetoImpuesto <= 0 || + !TryParseDecimal( + TxtPorcentajeIvaDetallePago.Text, + out porcentajeIvaCapturado) || + porcentajeIvaCapturado < 0 || + porcentajeIvaCapturado > 100) { Alert( - "Seleccione el producto o servicio, la unidad de medida y capture una cantidad mayor que cero.", + "Seleccione el producto o servicio, la unidad de medida y el objeto de impuesto; capture una cantidad, un importe y un IVA válidos.", TipoMensaje.MsgAdvertencia); Show("ModalPaymentDetail"); return; @@ -761,7 +770,15 @@ namespace SIG.Web.Pages.Catalogos CveTramitePago = CveTramitePagoSeleccionado, CvePS = cveProductoServicio, CveUM = cveUnidadMedida, - Cantidad = decimal.Round(cantidad, 4) + Cantidad = decimal.Round(cantidad, 4), + Importe = decimal.Round(importe, 2), + CveObjetoImpuesto = cveObjetoImpuesto, + PorcentajeIVA = cveObjetoImpuesto == 1 + ? 0m + : decimal.Round( + porcentajeIvaCapturado / 100m, + 6), + FechaInicioVigencia = DateTime.Today }; DbResult result = @@ -799,6 +816,9 @@ namespace SIG.Web.Pages.Catalogos cveProductoServicio <= 0) { DdlUnidadMedidaDetallePago.SelectedIndex = 0; + TxtPaymentDetailAmount.Text = string.Empty; + DdlObjetoImpuestoDetallePago.SelectedIndex = 0; + TxtPorcentajeIvaDetallePago.Text = "16.00"; UpPaymentDetailModal.Update(); Show("ModalPaymentDetail"); return; @@ -813,6 +833,9 @@ namespace SIG.Web.Pages.Catalogos productoServicio.CveUnidadMedida <= 0) { DdlUnidadMedidaDetallePago.SelectedIndex = 0; + TxtPaymentDetailAmount.Text = string.Empty; + DdlObjetoImpuestoDetallePago.SelectedIndex = 0; + TxtPorcentajeIvaDetallePago.Text = "16.00"; UpPaymentDetailModal.Update(); Show("ModalPaymentDetail"); return; @@ -821,58 +844,22 @@ namespace SIG.Web.Pages.Catalogos SelectDropDownValue( DdlUnidadMedidaDetallePago, productoServicio.CveUnidadMedida); + TxtPaymentDetailAmount.Text = + productoServicio.Precio.ToString( + "0.00", + CultureInfo.InvariantCulture); + SelectDropDownValue( + DdlObjetoImpuestoDetallePago, + productoServicio.CveObjetoImpuesto); + TxtPorcentajeIvaDetallePago.Text = + (productoServicio.PorcentajeIVA * 100m).ToString( + "0.####", + CultureInfo.InvariantCulture); UpPaymentDetailModal.Update(); Show("ModalPaymentDetail"); } - protected void BtnSaveGlobalPrice_Click(object sender, EventArgs e) - { - SetActiveTab(2); - decimal price; - if (!ChkConfirmGlobalPrice.Checked) - { - Alert( - "Confirme que comprende que el precio cambiará a nivel global.", - TipoMensaje.MsgAdvertencia); - Show("ModalGlobalPrice"); - return; - } - - if (!TryParseDecimal(TxtGlobalPrice.Text, out price) || - price < 0) - { - Alert( - "Capture un precio válido mayor o igual que cero.", - TipoMensaje.MsgAdvertencia); - Show("ModalGlobalPrice"); - return; - } - - DbResult result = - AppServices.TramiteCatalogService.UpdateProductoServicioPrecio( - CveProductoPrecioActual, - decimal.Round(price, 2), - CurrentUser.CveUsuario, - CurrentUser.NombreDominio); - - if (!result.Success) - { - Alert(result.UserMessage, TipoMensaje.MsgAdvertencia); - Show("ModalGlobalPrice"); - return; - } - - BindProducts(); - BindPayments(); - BindPaymentDetails(); - UpTabs.Update(); - Hide("ModalGlobalPrice"); - Alert( - "Precio global actualizado correctamente.", - TipoMensaje.ToastExito); - } - private void LoadProcedure() { DbResult result = @@ -1426,6 +1413,17 @@ namespace SIG.Web.Pages.Catalogos result.Data.Cantidad.ToString( "0.####", CultureInfo.InvariantCulture); + TxtPaymentDetailAmount.Text = + result.Data.Importe.ToString( + "0.00", + CultureInfo.InvariantCulture); + SelectDropDownValue( + DdlObjetoImpuestoDetallePago, + result.Data.CveObjetoImpuesto); + TxtPorcentajeIvaDetallePago.Text = + (result.Data.PorcentajeIVA * 100m).ToString( + "0.####", + CultureInfo.InvariantCulture); BindPaymentDetails(); UpPaymentDetailModal.Update(); Show("ModalPaymentDetail"); @@ -1453,48 +1451,6 @@ namespace SIG.Web.Pages.Catalogos TipoMensaje.ToastExito); } - private void EditGlobalPrice(int paymentDetailId) - { - DbResult detailResult = - AppServices.TramiteCatalogService.GetPagoDetalle( - paymentDetailId, - CurrentUser.CveUsuario, - CurrentUser.NombreDominio); - - if (!detailResult.Success || detailResult.Data == null) - { - Alert( - detailResult.UserMessage, - TipoMensaje.MsgAdvertencia); - return; - } - - DbResult productResult = - AppServices.TramiteCatalogService.GetProductoServicio( - detailResult.Data.CvePS, - CurrentUser.CveUsuario, - CurrentUser.NombreDominio); - - if (!productResult.Success || productResult.Data == null) - { - Alert( - productResult.UserMessage, - TipoMensaje.MsgAdvertencia); - return; - } - - CveProductoPrecioActual = productResult.Data.CvePS; - LitGlobalPriceProduct.Text = - Server.HtmlEncode(productResult.Data.ProductoDisplay); - TxtGlobalPrice.Text = - productResult.Data.Precio.ToString( - "0.00", - CultureInfo.InvariantCulture); - ChkConfirmGlobalPrice.Checked = false; - UpGlobalPriceModal.Update(); - Show("ModalGlobalPrice"); - } - private void ClearOptionsPanel() { CvePreguntaOpcionesSeleccionada = 0; diff --git a/SIG.Web/Pages/Ventas/VentaTramite.aspx.cs b/SIG.Web/Pages/Ventas/VentaTramite.aspx.cs index 59a40e1..3d255e8 100644 --- a/SIG.Web/Pages/Ventas/VentaTramite.aspx.cs +++ b/SIG.Web/Pages/Ventas/VentaTramite.aspx.cs @@ -648,16 +648,14 @@ namespace SIG.Web.Pages.Ventas GvConceptos.DataBind(); decimal subtotal = conceptos.Sum( - concepto => - Math.Round( - concepto.Cantidad * concepto.PrecioUnitario, - 2)); + concepto => concepto.Importe); decimal descuento = conceptos.Sum( concepto => concepto.Descuento); decimal iva = conceptos.Sum( concepto => concepto.IVA); - decimal total = conceptos.Sum( - concepto => concepto.Importe); + decimal total = Math.Round( + subtotal - descuento + iva, + 2); TotalVentaActual = total; @@ -689,6 +687,16 @@ namespace SIG.Web.Pages.Ventas venta.VentaGenerada && !venta.VentaTimbrada && TienePermiso(PermisoTimbrar); + LnkDescargarXmlSinTimbrar.Visible = + venta.CveCfdiDocumento.HasValue && + venta.CveCfdiDocumento.Value > 0; + LnkDescargarXmlSinTimbrar.NavigateUrl = + LnkDescargarXmlSinTimbrar.Visible + ? ResolveUrl( + "~/DescargaXmlCfdi.ashx?documento=" + + venta.CveCfdiDocumento.Value.ToString( + CultureInfo.InvariantCulture)) + : string.Empty; PnlCancelarTimbre.Visible = venta.VentaTimbrada && TienePermiso(PermisoCancelarTimbre); diff --git a/SIG.Web/Web.config b/SIG.Web/Web.config index a6c4b60..93a66c4 100644 --- a/SIG.Web/Web.config +++ b/SIG.Web/Web.config @@ -36,6 +36,7 @@ + @@ -79,6 +80,7 @@ + diff --git a/Ventas_Prueba_Limpieza_20260901.sql b/Ventas_Prueba_Limpieza_20260901.sql new file mode 100644 index 0000000..754ba7b --- /dev/null +++ b/Ventas_Prueba_Limpieza_20260901.sql @@ -0,0 +1,86 @@ +SET NOCOUNT ON; +SET XACT_ABORT ON; + +BEGIN TRANSACTION; + +CREATE TABLE #VentasPrueba +( + CveVenta int NOT NULL PRIMARY KEY +); + +INSERT INTO #VentasPrueba +( + CveVenta +) +SELECT DISTINCT + VD.CveVenta +FROM dbo.VentasDetalle AS VD +WHERE VD.CveSolicitud IS NOT NULL + OR VD.CveCostoTramite IS NOT NULL + OR VD.EsFondoInicial = 1; + +SELECT + V.CveVenta, + V.Serie, + V.Folio, + V.Fecha, + V.SubTotal, + V.Descuento, + V.IVA, + V.Total +FROM dbo.Ventas AS V +INNER JOIN #VentasPrueba AS VP + ON VP.CveVenta = V.CveVenta +ORDER BY V.CveVenta; + +IF EXISTS +( + SELECT 1 + FROM dbo.Vta_Pagos AS P + INNER JOIN #VentasPrueba AS VP + ON VP.CveVenta = P.CveVenta +) +BEGIN + THROW 50002, 'La selección incluye ventas con pagos; la limpieza de prueba se canceló.', 1; +END; + +IF EXISTS +( + SELECT 1 + FROM dbo.Fac_CfdiDocumentos AS D + INNER JOIN #VentasPrueba AS VP + ON VP.CveVenta = D.CveOrigen + WHERE D.TipoOrigen = 'VENTA' +) +BEGIN + THROW 50003, 'La selección incluye ventas con documentos CFDI; la limpieza de prueba se canceló.', 1; +END; + +DELETE VDI +FROM dbo.VentasDetalleImpuestos AS VDI +INNER JOIN dbo.VentasDetalle AS VD + ON VD.CveVentaDetalle = VDI.CveVentaDetalle +INNER JOIN #VentasPrueba AS VP + ON VP.CveVenta = VD.CveVenta; + +DELETE VD +FROM dbo.VentasDetalle AS VD +INNER JOIN #VentasPrueba AS VP + ON VP.CveVenta = VD.CveVenta; + +DELETE SP +FROM dbo.Tra_SolicitudesPagos AS SP +INNER JOIN #VentasPrueba AS VP + ON VP.CveVenta = SP.CveVenta; + +DELETE V +FROM dbo.Ventas AS V +INNER JOIN #VentasPrueba AS VP + ON VP.CveVenta = V.CveVenta; + +SELECT + @@ROWCOUNT AS VentasQueSeEliminarian; + +ROLLBACK TRANSACTION; + +-- Cambiar únicamente a COMMIT TRANSACTION después de revisar la selección.