Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Abstractions/IEmployeeService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Threading.Tasks;
using Fiscalapi.Common;
using Fiscalapi.Models;

namespace Fiscalapi.Abstractions
{
/// <summary>
/// Interfaz para el servicio de Empleado — gestiona los datos del empleado anidados bajo una Persona.
/// Patrón de endpoint: api/{version}/people/{personId}/employee
/// </summary>
public interface IEmployeeService
{
Task<ApiResponse<EmployeeData>> GetByIdAsync(string id);
Task<ApiResponse<EmployeeData>> CreateAsync(EmployeeData requestModel);
Task<ApiResponse<EmployeeData>> UpdateAsync(EmployeeData requestModel);
Task<ApiResponse<bool>> DeleteAsync(string id);
}
}
18 changes: 18 additions & 0 deletions Abstractions/IEmployerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Threading.Tasks;
using Fiscalapi.Common;
using Fiscalapi.Models;

namespace Fiscalapi.Abstractions
{
/// <summary>
/// Interfaz para el servicio de Empleador — gestiona los datos del empleador anidados bajo una Persona.
/// Patrón de endpoint: api/{version}/people/{personId}/employer
/// </summary>
public interface IEmployerService
{
Task<ApiResponse<EmployerData>> GetByIdAsync(string id);
Task<ApiResponse<EmployerData>> CreateAsync(EmployerData requestModel);
Task<ApiResponse<EmployerData>> UpdateAsync(EmployerData requestModel);
Task<ApiResponse<bool>> DeleteAsync(string id);
}
}
5 changes: 2 additions & 3 deletions Abstractions/IPersonService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Fiscalapi.Models;
using Fiscalapi.Services;

namespace Fiscalapi.Abstractions
{
Expand All @@ -8,7 +7,7 @@ namespace Fiscalapi.Abstractions
/// </summary>
public interface IPersonService : IFiscalApiService<Person>
{
EmployerService Employer { get; }
EmployeeService Employee { get; }
IEmployerService Employer { get; }
IEmployeeService Employee { get; }
}
}
14 changes: 14 additions & 0 deletions Models/Payroll.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,27 @@ public class PayrollRetirement
public decimal NonAccumulableIncome { get; set; }
}

public class PayrollStockOptions
{
/// <summary>
/// Valor de mercado de las acciones o títulos valor al momento de ejercer la opción.
/// </summary>
public decimal MarketPrice { get; set; }

/// <summary>
/// Precio pactado al otorgarse la opción de ingresos en acciones o títulos valor.
/// </summary>
public decimal GrantPrice { get; set; }
}

public class PayrollEarning
{
public string EarningTypeCode { get; set; }
public string Code { get; set; }
public string Concept { get; set; }
public decimal TaxedAmount { get; set; }
public decimal ExemptAmount { get; set; }
public PayrollStockOptions StockOptions { get; set; }
public List<PayrollEarningOvertime> Overtime { get; set; }
}

Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Comment on lines +39 to +40
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Parenthetical label "CFDI Nómina" reads as a detached fragment.

Both lines use the pattern a una persona. CFDI Nómina) — a full stop inside the parentheses followed by a bare noun phrase, which creates an awkward sentence fragment. Replacing the period with a conjunction keeps the parenthetical cohesive:

✏️ Suggested wording
-  - **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)
+  - **Datos de empleado** (agrega/actualiza/elimina datos de empleado a una persona para CFDI Nómina)
+  - **Datos de empleador** (agrega/actualiza/elimina datos de empleador a una persona para CFDI Nómina)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- **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)
- **Datos de empleado** (agrega/actualiza/elimina datos de empleado a una persona para CFDI Nómina)
- **Datos de empleador** (agrega/actualiza/elimina datos de empleador a una persona para CFDI Nómina)
🧰 Tools
🪛 LanguageTool

[grammar] ~39-~39: Aquí puede haber un error.
Context: ... de empleado a una persona. CFDI Nómina) - Datos de empleador (agrega/actualiza/e...

(QB_NEW_ES)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 39 - 40, The parenthetical fragment in the two bullet
points for "Datos de empleado" and "Datos de empleador" should be made
grammatically cohesive by removing the period inside the parentheses and joining
the phrase; update both lines so the parenthetical reads as a modifier (e.g.,
change "a una persona. CFDI Nómina)" to "a una persona para CFDI Nómina)" or to
"a una persona (CFDI Nómina)"), ensuring the sentence flows without the detached
fragment.


## 🎖️ 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
Expand All @@ -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
Expand Down
51 changes: 32 additions & 19 deletions Services/EmployeeService.cs
Original file line number Diff line number Diff line change
@@ -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<ApiResponse<EmployeeData>> GetByIdAsync(string id)
{
string endpoint = $"{baseEndpoint}/{id}/employee";

return await HttpClient.GetAsync<EmployeeData>(endpoint);
if (string.IsNullOrWhiteSpace(id))
throw new ArgumentException("Employee person ID is required to build endpoint", nameof(id));
return await HttpClient.GetAsync<EmployeeData>(BuildEndpoint(id));
}

//POST /api/v4/people/{personId}/employee
// POST /api/{version}/people/{personId}/employee
public async Task<ApiResponse<EmployeeData>> CreateAsync(EmployeeData requestModel)
{
string endpoint = $"{baseEndpoint}/{requestModel.EmployeePersonId}/employee";

return await HttpClient.PostAsync<EmployeeData>(endpoint, requestModel);
ValidateRequestModel(requestModel);
return await HttpClient.PostAsync<EmployeeData>(BuildEndpoint(requestModel.EmployeePersonId), requestModel);
}

// PUT /api/v4/people/{personId}/employee
// PUT /api/{version}/people/{personId}/employee
public async Task<ApiResponse<EmployeeData>> UpdateAsync(EmployeeData requestModel)
{
string endpoint = $"{baseEndpoint}/{requestModel.EmployeePersonId}/employee";

return await HttpClient.PutAsync<EmployeeData>(endpoint, requestModel);
ValidateRequestModel(requestModel);
return await HttpClient.PutAsync<EmployeeData>(BuildEndpoint(requestModel.EmployeePersonId), requestModel);
}

// DELETE /api/v4/people/{personId}/employee
// DELETE /api/{version}/people/{personId}/employee
public async Task<ApiResponse<bool>> 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));
}
}
}
51 changes: 32 additions & 19 deletions Services/EmployerService.cs
Original file line number Diff line number Diff line change
@@ -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<ApiResponse<EmployerData>> GetByIdAsync(string id)
{
string endpoint = $"{baseEndpoint}/{id}/employer";

return await HttpClient.GetAsync<EmployerData>(endpoint);
if (string.IsNullOrWhiteSpace(id))
throw new ArgumentException("Person ID is required to build endpoint", nameof(id));
return await HttpClient.GetAsync<EmployerData>(BuildEndpoint(id));
}

//POST /api/v4/people/{personId}/employer
// POST /api/{version}/people/{personId}/employer
public async Task<ApiResponse<EmployerData>> CreateAsync(EmployerData requestModel)
{
string endpoint = $"{baseEndpoint}/{requestModel.PersonId}/employer";

return await HttpClient.PostAsync<EmployerData>(endpoint, requestModel);
ValidateRequestModel(requestModel);
return await HttpClient.PostAsync<EmployerData>(BuildEndpoint(requestModel.PersonId), requestModel);
}

// PUT /api/v4/people/{personId}/employer
// PUT /api/{version}/people/{personId}/employer
public async Task<ApiResponse<EmployerData>> UpdateAsync(EmployerData requestModel)
{
string endpoint = $"{baseEndpoint}/{requestModel.PersonId}/employer";

return await HttpClient.PutAsync<EmployerData>(endpoint, requestModel);
ValidateRequestModel(requestModel);
return await HttpClient.PutAsync<EmployerData>(BuildEndpoint(requestModel.PersonId), requestModel);
}

// DELETE /api/v4/people/{personId}/employer
// DELETE /api/{version}/people/{personId}/employer
public async Task<ApiResponse<bool>> 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));
}
}
}
8 changes: 4 additions & 4 deletions Services/PersonService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ namespace Fiscalapi.Services
{
public class PersonService : BaseFiscalApiService<Person>, 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);
}
}
}
18 changes: 18 additions & 0 deletions Services/StampService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Fiscalapi.Common;
using Fiscalapi.Http;
using Fiscalapi.Models;
using System;
using System.Threading.Tasks;

namespace Fiscalapi.Services
Expand All @@ -15,12 +16,29 @@ public StampService(IFiscalApiHttpClient httpClient, string apiVersion)

public async Task<ApiResponse<bool>> TransferStamps(StampTransactionParams requestModel)
{
ValidateRequest(requestModel);
return await HttpClient.PostAsync<bool>(BuildEndpoint(), requestModel);
}

public async Task<ApiResponse<bool>> WithdrawStamps(StampTransactionParams requestModel)
{
ValidateRequest(requestModel);
return await HttpClient.PostAsync<bool>(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));
}
}
}
2 changes: 1 addition & 1 deletion fiscalapi-net.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net461;net48;netcoreapp3.1;net5.0;net6.0;net8.0</TargetFrameworks>
<Version>4.0.360</Version>
<Version>4.0.361</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<PackageVersion>$(Version)</PackageVersion>
Expand Down