From 978df347db150ae99cee68ae9290415e40560fcc Mon Sep 17 00:00:00 2001 From: Jose Antonio Medina Date: Fri, 20 Feb 2026 20:57:03 -0600 Subject: [PATCH 01/11] Added interfaces for Employee and Employer services --- Abstractions/IEmployeeService.cs | 18 ++++++++++++++++++ Abstractions/IEmployerService.cs | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 Abstractions/IEmployeeService.cs create mode 100644 Abstractions/IEmployerService.cs diff --git a/Abstractions/IEmployeeService.cs b/Abstractions/IEmployeeService.cs new file mode 100644 index 0000000..63b5191 --- /dev/null +++ b/Abstractions/IEmployeeService.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Fiscalapi.Common; +using Fiscalapi.Models; + +namespace Fiscalapi.Abstractions +{ + /// + /// Interfaz para el servicio de Empleado — gestiona los datos del empleado anidados bajo una Persona. + /// Patrón de endpoint: api/{version}/people/{personId}/employee + /// + public interface IEmployeeService + { + Task> GetByIdAsync(string id); + Task> CreateAsync(EmployeeData requestModel); + Task> UpdateAsync(EmployeeData requestModel); + Task> DeleteAsync(string id); + } +} diff --git a/Abstractions/IEmployerService.cs b/Abstractions/IEmployerService.cs new file mode 100644 index 0000000..d3f899d --- /dev/null +++ b/Abstractions/IEmployerService.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Fiscalapi.Common; +using Fiscalapi.Models; + +namespace Fiscalapi.Abstractions +{ + /// + /// Interfaz para el servicio de Empleador — gestiona los datos del empleador anidados bajo una Persona. + /// Patrón de endpoint: api/{version}/people/{personId}/employer + /// + public interface IEmployerService + { + Task> GetByIdAsync(string id); + Task> CreateAsync(EmployerData requestModel); + Task> UpdateAsync(EmployerData requestModel); + Task> DeleteAsync(string id); + } +} From a2c4fe8bf0813579cc7d7c4f55b61e631936f687 Mon Sep 17 00:00:00 2001 From: Jose Antonio Medina Date: Fri, 20 Feb 2026 20:57:49 -0600 Subject: [PATCH 02/11] Updated Employer and Employee service references in IPersonService --- Abstractions/IPersonService.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Abstractions/IPersonService.cs b/Abstractions/IPersonService.cs index 32176bb..deb7b90 100644 --- a/Abstractions/IPersonService.cs +++ b/Abstractions/IPersonService.cs @@ -1,5 +1,4 @@ using Fiscalapi.Models; -using Fiscalapi.Services; namespace Fiscalapi.Abstractions { @@ -8,7 +7,7 @@ namespace Fiscalapi.Abstractions /// public interface IPersonService : IFiscalApiService { - EmployerService Employer { get; } - EmployeeService Employee { get; } + IEmployerService Employer { get; } + IEmployeeService Employee { get; } } } \ No newline at end of file From ca03a24face068bcda5877bd91e20cbbabd6920e Mon Sep 17 00:00:00 2001 From: Jose Antonio Medina Date: Fri, 20 Feb 2026 20:59:29 -0600 Subject: [PATCH 03/11] Added missing stock options class in payroll earning --- Models/Payroll.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Models/Payroll.cs b/Models/Payroll.cs index 1c722fd..42f92e9 100644 --- a/Models/Payroll.cs +++ b/Models/Payroll.cs @@ -61,6 +61,19 @@ public class PayrollRetirement public decimal NonAccumulableIncome { get; set; } } + public class PayrollStockOptions + { + /// + /// Valor de mercado de las acciones o títulos valor al momento de ejercer la opción. + /// + public decimal MarketPrice { get; set; } + + /// + /// Precio pactado al otorgarse la opción de ingresos en acciones o títulos valor. + /// + public decimal GrantPrice { get; set; } + } + public class PayrollEarning { public string EarningTypeCode { get; set; } @@ -68,6 +81,7 @@ public class PayrollEarning public string Concept { get; set; } public decimal TaxedAmount { get; set; } public decimal ExemptAmount { get; set; } + public PayrollStockOptions StockOptions { get; set; } public List Overtime { get; set; } } From 2ae3bb3d08ac8990d0719cec5c9c96fb3304e6d0 Mon Sep 17 00:00:00 2001 From: Jose Antonio Medina Date: Fri, 20 Feb 2026 21:04:25 -0600 Subject: [PATCH 04/11] Removed hardcoded api version from Employee and Employer services, updated Person Service references for employee and employer service --- Services/EmployeeService.cs | 45 ++++++++++++++----------------------- Services/EmployerService.cs | 45 ++++++++++++++----------------------- Services/PersonService.cs | 8 +++---- 3 files changed, 38 insertions(+), 60 deletions(-) diff --git a/Services/EmployeeService.cs b/Services/EmployeeService.cs index 6ec3457..30418a8 100644 --- a/Services/EmployeeService.cs +++ b/Services/EmployeeService.cs @@ -1,50 +1,39 @@ -using Fiscalapi.Common; +using Fiscalapi.Abstractions; +using Fiscalapi.Common; using Fiscalapi.Http; using Fiscalapi.Models; using System.Threading.Tasks; namespace Fiscalapi.Services { - public class EmployeeService + public class EmployeeService : IEmployeeService { private IFiscalApiHttpClient HttpClient { get; } - private string baseEndpoint = "api/v4/people"; + private readonly string _baseEndpoint; - public EmployeeService(IFiscalApiHttpClient fiscalApiHttpClient) + public EmployeeService(IFiscalApiHttpClient fiscalApiHttpClient, string apiVersion) { HttpClient = fiscalApiHttpClient; + _baseEndpoint = $"api/{apiVersion}/people"; } - // GET /api/v4/people/{personId}/employee - public async Task> GetByIdAsync(string id) - { - string endpoint = $"{baseEndpoint}/{id}/employee"; + private string BuildEndpoint(string personId) + => $"{_baseEndpoint}/{personId}/employee"; - return await HttpClient.GetAsync(endpoint); - } + // GET /api/{version}/people/{personId}/employee + public async Task> GetByIdAsync(string id) + => await HttpClient.GetAsync(BuildEndpoint(id)); - //POST /api/v4/people/{personId}/employee + // POST /api/{version}/people/{personId}/employee public async Task> CreateAsync(EmployeeData requestModel) - { - string endpoint = $"{baseEndpoint}/{requestModel.EmployeePersonId}/employee"; - - return await HttpClient.PostAsync(endpoint, requestModel); - } + => await HttpClient.PostAsync(BuildEndpoint(requestModel.EmployeePersonId), requestModel); - // PUT /api/v4/people/{personId}/employee + // PUT /api/{version}/people/{personId}/employee public async Task> UpdateAsync(EmployeeData requestModel) - { - string endpoint = $"{baseEndpoint}/{requestModel.EmployeePersonId}/employee"; - - return await HttpClient.PutAsync(endpoint, requestModel); - } + => await HttpClient.PutAsync(BuildEndpoint(requestModel.EmployeePersonId), requestModel); - // DELETE /api/v4/people/{personId}/employee + // DELETE /api/{version}/people/{personId}/employee public async Task> DeleteAsync(string id) - { - string endpoint = $"{baseEndpoint}/{id}/employee"; - - return await HttpClient.DeleteAsync(endpoint); - } + => await HttpClient.DeleteAsync(BuildEndpoint(id)); } } diff --git a/Services/EmployerService.cs b/Services/EmployerService.cs index 2aea4f3..66e5aa1 100644 --- a/Services/EmployerService.cs +++ b/Services/EmployerService.cs @@ -1,50 +1,39 @@ -using Fiscalapi.Common; +using Fiscalapi.Abstractions; +using Fiscalapi.Common; using Fiscalapi.Http; using Fiscalapi.Models; using System.Threading.Tasks; namespace Fiscalapi.Services { - public class EmployerService + public class EmployerService : IEmployerService { private IFiscalApiHttpClient HttpClient { get; } - private string baseEndpoint = "api/v4/people"; + private readonly string _baseEndpoint; - public EmployerService(IFiscalApiHttpClient fiscalApiHttpClient) + public EmployerService(IFiscalApiHttpClient fiscalApiHttpClient, string apiVersion) { HttpClient = fiscalApiHttpClient; + _baseEndpoint = $"api/{apiVersion}/people"; } - // GET /api/v4/people/{personId}/employer - public async Task> GetByIdAsync(string id) - { - string endpoint = $"{baseEndpoint}/{id}/employer"; + private string BuildEndpoint(string personId) + => $"{_baseEndpoint}/{personId}/employer"; - return await HttpClient.GetAsync(endpoint); - } + // GET /api/{version}/people/{personId}/employer + public async Task> GetByIdAsync(string id) + => await HttpClient.GetAsync(BuildEndpoint(id)); - //POST /api/v4/people/{personId}/employer + // POST /api/{version}/people/{personId}/employer public async Task> CreateAsync(EmployerData requestModel) - { - string endpoint = $"{baseEndpoint}/{requestModel.PersonId}/employer"; - - return await HttpClient.PostAsync(endpoint, requestModel); - } + => await HttpClient.PostAsync(BuildEndpoint(requestModel.PersonId), requestModel); - // PUT /api/v4/people/{personId}/employer + // PUT /api/{version}/people/{personId}/employer public async Task> UpdateAsync(EmployerData requestModel) - { - string endpoint = $"{baseEndpoint}/{requestModel.PersonId}/employer"; - - return await HttpClient.PutAsync(endpoint, requestModel); - } + => await HttpClient.PutAsync(BuildEndpoint(requestModel.PersonId), requestModel); - // DELETE /api/v4/people/{personId}/employer + // DELETE /api/{version}/people/{personId}/employer public async Task> DeleteAsync(string id) - { - string endpoint = $"{baseEndpoint}/{id}/employer"; - - return await HttpClient.DeleteAsync(endpoint); - } + => await HttpClient.DeleteAsync(BuildEndpoint(id)); } } diff --git a/Services/PersonService.cs b/Services/PersonService.cs index ba58c1a..8d0a36e 100644 --- a/Services/PersonService.cs +++ b/Services/PersonService.cs @@ -6,14 +6,14 @@ namespace Fiscalapi.Services { public class PersonService : BaseFiscalApiService, IPersonService { - public EmployerService Employer { get; } - public EmployeeService Employee { get; } + public IEmployerService Employer { get; } + public IEmployeeService Employee { get; } public PersonService(IFiscalApiHttpClient httpClient, string apiVersion) : base(httpClient, "people", apiVersion) { - Employer = new EmployerService(httpClient); - Employee = new EmployeeService(httpClient); + Employer = new EmployerService(httpClient, apiVersion); + Employee = new EmployeeService(httpClient, apiVersion); } } } \ No newline at end of file From f221478f6f5ece3492f83ef85353e4548f8ab5ec Mon Sep 17 00:00:00 2001 From: Jose Antonio Medina Date: Fri, 20 Feb 2026 21:12:34 -0600 Subject: [PATCH 05/11] Added request validation for Employee and Employer services --- Services/EmployeeService.cs | 31 +++++++++++++++++++++++++++---- Services/EmployerService.cs | 31 +++++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/Services/EmployeeService.cs b/Services/EmployeeService.cs index 30418a8..d17b2eb 100644 --- a/Services/EmployeeService.cs +++ b/Services/EmployeeService.cs @@ -22,18 +22,41 @@ private string BuildEndpoint(string personId) // GET /api/{version}/people/{personId}/employee public async Task> GetByIdAsync(string id) - => await HttpClient.GetAsync(BuildEndpoint(id)); + { + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentException("Employee person ID is required to build endpoint", nameof(id)); + return await HttpClient.GetAsync(BuildEndpoint(id)); + } // POST /api/{version}/people/{personId}/employee public async Task> CreateAsync(EmployeeData requestModel) - => await HttpClient.PostAsync(BuildEndpoint(requestModel.EmployeePersonId), requestModel); + { + ValidateRequestModel(requestModel); + return await HttpClient.PostAsync(BuildEndpoint(requestModel.EmployeePersonId), requestModel); + } // PUT /api/{version}/people/{personId}/employee public async Task> UpdateAsync(EmployeeData requestModel) - => await HttpClient.PutAsync(BuildEndpoint(requestModel.EmployeePersonId), requestModel); + { + ValidateRequestModel(requestModel); + return await HttpClient.PutAsync(BuildEndpoint(requestModel.EmployeePersonId), requestModel); + } // DELETE /api/{version}/people/{personId}/employee public async Task> DeleteAsync(string id) - => await HttpClient.DeleteAsync(BuildEndpoint(id)); + { + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentException("El ID de la persona requerido", nameof(id)); + return await HttpClient.DeleteAsync(BuildEndpoint(id)); + } + + private void ValidateRequestModel(EmployeeData requestModel) + { + if (requestModel == null) + throw new ArgumentNullException(nameof(requestModel), "El modelo de solicitud no puede ser nulo"); + + if (string.IsNullOrWhiteSpace(requestModel.EmployeePersonId)) + throw new ArgumentException("Se requiere el ID de la persona para crear o actualizar los datos del empleado", nameof(requestModel.EmployeePersonId)); + } } } diff --git a/Services/EmployerService.cs b/Services/EmployerService.cs index 66e5aa1..a79ee81 100644 --- a/Services/EmployerService.cs +++ b/Services/EmployerService.cs @@ -22,18 +22,41 @@ private string BuildEndpoint(string personId) // GET /api/{version}/people/{personId}/employer public async Task> GetByIdAsync(string id) - => await HttpClient.GetAsync(BuildEndpoint(id)); + { + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentException("Person ID is required to build endpoint", nameof(id)); + return await HttpClient.GetAsync(BuildEndpoint(id)); + } // POST /api/{version}/people/{personId}/employer public async Task> CreateAsync(EmployerData requestModel) - => await HttpClient.PostAsync(BuildEndpoint(requestModel.PersonId), requestModel); + { + ValidateRequestModel(requestModel); + return await HttpClient.PostAsync(BuildEndpoint(requestModel.PersonId), requestModel); + } // PUT /api/{version}/people/{personId}/employer public async Task> UpdateAsync(EmployerData requestModel) - => await HttpClient.PutAsync(BuildEndpoint(requestModel.PersonId), requestModel); + { + ValidateRequestModel(requestModel); + return await HttpClient.PutAsync(BuildEndpoint(requestModel.PersonId), requestModel); + } // DELETE /api/{version}/people/{personId}/employer public async Task> DeleteAsync(string id) - => await HttpClient.DeleteAsync(BuildEndpoint(id)); + { + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentException("El ID de la persona requerido", nameof(id)); + return await HttpClient.DeleteAsync(BuildEndpoint(id)); + } + + private void ValidateRequestModel(EmployerData requestModel) + { + if (requestModel == null) + throw new ArgumentNullException(nameof(requestModel), "El modelo de solicitud no puede ser nulo"); + + if (string.IsNullOrWhiteSpace(requestModel.PersonId)) + throw new ArgumentException("Se requiere el ID de la persona para crear o actualizar los datos del empleador", nameof(requestModel.PersonId)); + } } } From eb40a70a11200f48e52ff0e02d63d49042d287b0 Mon Sep 17 00:00:00 2001 From: Jose Antonio Medina Date: Fri, 20 Feb 2026 21:15:13 -0600 Subject: [PATCH 06/11] Added request validation for Stamp Service --- Services/StampService.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Services/StampService.cs b/Services/StampService.cs index 1c8b6f6..4fca3c8 100644 --- a/Services/StampService.cs +++ b/Services/StampService.cs @@ -15,12 +15,29 @@ public StampService(IFiscalApiHttpClient httpClient, string apiVersion) public async Task> TransferStamps(StampTransactionParams requestModel) { + ValidateRequest(requestModel); return await HttpClient.PostAsync(BuildEndpoint(), requestModel); } public async Task> WithdrawStamps(StampTransactionParams requestModel) { + ValidateRequest(requestModel); return await HttpClient.PostAsync(BuildEndpoint(), requestModel); } + + private void ValidateRequest(StampTransactionParams requestModel) + { + if (requestModel == null) + throw new ArgumentNullException(nameof(requestModel), "No se acepta modelo de transaccion nulo"); + + if (string.IsNullOrWhiteSpace(requestModel.FromPersonId)) + throw new ArgumentException("Se requiere el ID de la persona de origen para la transferencia de timbres", nameof(requestModel.FromPersonId)); + + if (string.IsNullOrWhiteSpace(requestModel.ToPersonId)) + throw new ArgumentException("Se requiere el ID de la persona de destino para la transferencia de timbres", nameof(requestModel.ToPersonId)); + + if (requestModel.Amount <= 0) + throw new ArgumentException("La cantidad debe ser mayor que cero", nameof(requestModel.Amount)); + } } } From ee2242d23807b878d7c9671e9c1785ceda591018 Mon Sep 17 00:00:00 2001 From: Jose Antonio Medina Date: Fri, 20 Feb 2026 21:57:57 -0600 Subject: [PATCH 07/11] Updated readme.md --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b28b45a..7eda501 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ - **Soporte completo para CFDI 4.0** con todas las especificaciones oficiales - **Timbrado de facturas de ingreso** con validación automática - **Timbrado de notas de crédito** (facturas de egreso) -- **Timbrado de complementos de pago** en MXN, USD y EUR. +- **Timbrado de complementos de pago** en MXN, USD y EUR +- **Timbrado de facturas de nómina** - Soporte para los 13 tipos de CFDI de nómina - **Consulta del estatus de facturas** en el SAT en tiempo real - **Cancelación de facturas** - **Generación de archivos PDF** de las facturas con formato profesional @@ -35,17 +36,30 @@ - **Administración de personas** (emisores, receptores, clientes, usuarios, etc.) - **Gestión de certificados CSD y FIEL** (subir archivos .cer y .key a FiscalAPI) - **Configuración de datos fiscales** (RFC, domicilio fiscal, régimen fiscal) +- **Datos de empleado** (agrega/actualiza/elimina datos de empleado a una persona. CFDI Nómina) +- **Datos de empleador** (agrega/actualiza/elimina datos de empleador a una persona. CFDI Nómina) + +## 🎖️ Gestión de Timbres +- **Gestión de folios fiscales** Compra timbres a FiscalAPI y transfiere/retira a las personas de tu organización según tus reglas de negocio. ## 🛍️ Gestión de Productos/Servicios - **Gestión de productos y servicios** con catálogo personalizable - **Administración de impuestos aplicables** (IVA, ISR, IEPS) +- **Timbres** + Listar transacciones, transferir y retirar timbres entre personas. ## 📚 Consulta de Catálogos SAT - **Consulta en catálogos oficiales del SAT** actualizados - **Consulta en catálogos oficiales de Descarga masiva del SAT** actualizados - **Búsqueda de información** en catálogos del SAT con filtros avanzados - **Acceso y búsqueda** en catálogos completos - + +## 🎫 Gestión de Timbres +- **Listar transacciones de timbres** con paginación +- **Consultar transacciones** por ID +- **Transferir timbres** entre personas +- **Retirar timbres** de una persona + ## 📖 Recursos Adicionales - **Cientos de ejemplos de código** disponibles en múltiples lenguajes de programación - Documentación completa con guías paso a paso From 3fc78c6beeff276f00d75308a8cf3b9f17e17afd Mon Sep 17 00:00:00 2001 From: Jose Antonio Medina Date: Fri, 20 Feb 2026 21:58:37 -0600 Subject: [PATCH 08/11] Updated package version --- fiscalapi-net.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fiscalapi-net.csproj b/fiscalapi-net.csproj index eecd8e1..16e6110 100644 --- a/fiscalapi-net.csproj +++ b/fiscalapi-net.csproj @@ -2,7 +2,7 @@ netstandard2.0;net461;net48;netcoreapp3.1;net5.0;net6.0;net8.0 - 4.0.360 + 4.0.361 $(Version) $(Version) $(Version) From 5ac9c153008b11d37f3f3402e98acc9b850deab6 Mon Sep 17 00:00:00 2001 From: Jose Antonio Medina Date: Fri, 20 Feb 2026 22:05:10 -0600 Subject: [PATCH 09/11] Added missing imports --- Services/EmployeeService.cs | 1 + Services/EmployerService.cs | 1 + Services/StampService.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/Services/EmployeeService.cs b/Services/EmployeeService.cs index d17b2eb..eee993a 100644 --- a/Services/EmployeeService.cs +++ b/Services/EmployeeService.cs @@ -2,6 +2,7 @@ using Fiscalapi.Common; using Fiscalapi.Http; using Fiscalapi.Models; +using System; using System.Threading.Tasks; namespace Fiscalapi.Services diff --git a/Services/EmployerService.cs b/Services/EmployerService.cs index a79ee81..cb46388 100644 --- a/Services/EmployerService.cs +++ b/Services/EmployerService.cs @@ -2,6 +2,7 @@ using Fiscalapi.Common; using Fiscalapi.Http; using Fiscalapi.Models; +using System; using System.Threading.Tasks; namespace Fiscalapi.Services diff --git a/Services/StampService.cs b/Services/StampService.cs index 4fca3c8..71de1c4 100644 --- a/Services/StampService.cs +++ b/Services/StampService.cs @@ -2,6 +2,7 @@ using Fiscalapi.Common; using Fiscalapi.Http; using Fiscalapi.Models; +using System; using System.Threading.Tasks; namespace Fiscalapi.Services From 30cb22daf4581f7cf91c0c2af3a6e40d49a7d3ed Mon Sep 17 00:00:00 2001 From: Jose Antonio Medina Date: Fri, 20 Feb 2026 23:04:03 -0600 Subject: [PATCH 10/11] Removed duplicated entry for stamps management --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 7eda501..e88166d 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,6 @@ - **Búsqueda de información** en catálogos del SAT con filtros avanzados - **Acceso y búsqueda** en catálogos completos -## 🎫 Gestión de Timbres -- **Listar transacciones de timbres** con paginación -- **Consultar transacciones** por ID -- **Transferir timbres** entre personas -- **Retirar timbres** de una persona - ## 📖 Recursos Adicionales - **Cientos de ejemplos de código** disponibles en múltiples lenguajes de programación - Documentación completa con guías paso a paso From ee9032d7d48511c656a99a4bb332eb301ffc2671 Mon Sep 17 00:00:00 2001 From: Jose Antonio Medina Date: Fri, 20 Feb 2026 23:26:29 -0600 Subject: [PATCH 11/11] Removed duplicated entry for stamps --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index e88166d..80a0963 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,6 @@ ## 🛍️ Gestión de Productos/Servicios - **Gestión de productos y servicios** con catálogo personalizable - **Administración de impuestos aplicables** (IVA, ISR, IEPS) -- **Timbres** - Listar transacciones, transferir y retirar timbres entre personas. ## 📚 Consulta de Catálogos SAT - **Consulta en catálogos oficiales del SAT** actualizados