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);
+ }
+}
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
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; }
}
diff --git a/README.md b/README.md
index b28b45a..80a0963 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,6 +36,11 @@
- **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
@@ -45,7 +51,7 @@
- **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
-
+
## 📖 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
diff --git a/Services/EmployeeService.cs b/Services/EmployeeService.cs
index 6ec3457..eee993a 100644
--- a/Services/EmployeeService.cs
+++ b/Services/EmployeeService.cs
@@ -1,50 +1,63 @@
-using Fiscalapi.Common;
+using Fiscalapi.Abstractions;
+using Fiscalapi.Common;
using Fiscalapi.Http;
using Fiscalapi.Models;
+using System;
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
+ private string BuildEndpoint(string personId)
+ => $"{_baseEndpoint}/{personId}/employee";
+
+ // GET /api/{version}/people/{personId}/employee
public async Task> GetByIdAsync(string id)
{
- string endpoint = $"{baseEndpoint}/{id}/employee";
-
- return await HttpClient.GetAsync(endpoint);
+ 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/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);
+ ValidateRequestModel(requestModel);
+ return 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);
+ ValidateRequestModel(requestModel);
+ return 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";
+ 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");
- return await HttpClient.DeleteAsync(endpoint);
+ 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 2aea4f3..cb46388 100644
--- a/Services/EmployerService.cs
+++ b/Services/EmployerService.cs
@@ -1,50 +1,63 @@
-using Fiscalapi.Common;
+using Fiscalapi.Abstractions;
+using Fiscalapi.Common;
using Fiscalapi.Http;
using Fiscalapi.Models;
+using System;
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
+ private string BuildEndpoint(string personId)
+ => $"{_baseEndpoint}/{personId}/employer";
+
+ // GET /api/{version}/people/{personId}/employer
public async Task> GetByIdAsync(string id)
{
- string endpoint = $"{baseEndpoint}/{id}/employer";
-
- return await HttpClient.GetAsync(endpoint);
+ if (string.IsNullOrWhiteSpace(id))
+ throw new ArgumentException("Person ID is required to build endpoint", nameof(id));
+ return 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);
+ ValidateRequestModel(requestModel);
+ return 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);
+ ValidateRequestModel(requestModel);
+ return 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";
+ 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");
- return await HttpClient.DeleteAsync(endpoint);
+ 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));
}
}
}
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
diff --git a/Services/StampService.cs b/Services/StampService.cs
index 1c8b6f6..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
@@ -15,12 +16,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));
+ }
}
}
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)