feat(api): add impuestos API endpoints (IVA/ISR mensual y resumen)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
158
apps/api/src/services/impuestos.service.ts
Normal file
158
apps/api/src/services/impuestos.service.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { prisma } from '../config/database.js';
|
||||
import type { IvaMensual, IsrMensual, ResumenIva, ResumenIsr } from '@horux/shared';
|
||||
|
||||
export async function getIvaMensual(schema: string, año: number): Promise<IvaMensual[]> {
|
||||
const data = await prisma.$queryRawUnsafe<IvaMensual[]>(`
|
||||
SELECT
|
||||
id, año, mes,
|
||||
iva_trasladado as "ivaTrasladado",
|
||||
iva_acreditable as "ivaAcreditable",
|
||||
COALESCE(iva_retenido, 0) as "ivaRetenido",
|
||||
resultado, acumulado, estado,
|
||||
fecha_declaracion as "fechaDeclaracion"
|
||||
FROM "${schema}".iva_mensual
|
||||
WHERE año = $1
|
||||
ORDER BY mes
|
||||
`, año);
|
||||
|
||||
return data.map(row => ({
|
||||
...row,
|
||||
ivaTrasladado: Number(row.ivaTrasladado),
|
||||
ivaAcreditable: Number(row.ivaAcreditable),
|
||||
ivaRetenido: Number(row.ivaRetenido),
|
||||
resultado: Number(row.resultado),
|
||||
acumulado: Number(row.acumulado),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getResumenIva(schema: string, año: number, mes: number): Promise<ResumenIva> {
|
||||
// Get from iva_mensual if exists
|
||||
const existing = await prisma.$queryRawUnsafe<any[]>(`
|
||||
SELECT * FROM "${schema}".iva_mensual WHERE año = $1 AND mes = $2
|
||||
`, año, mes);
|
||||
|
||||
if (existing && existing.length > 0) {
|
||||
const record = existing[0];
|
||||
const [acumuladoResult] = await prisma.$queryRawUnsafe<[{ total: number }]>(`
|
||||
SELECT COALESCE(SUM(resultado), 0) as total
|
||||
FROM "${schema}".iva_mensual
|
||||
WHERE año = $1 AND mes <= $2
|
||||
`, año, mes);
|
||||
|
||||
return {
|
||||
trasladado: Number(record.iva_trasladado || 0),
|
||||
acreditable: Number(record.iva_acreditable || 0),
|
||||
retenido: Number(record.iva_retenido || 0),
|
||||
resultado: Number(record.resultado || 0),
|
||||
acumuladoAnual: Number(acumuladoResult?.total || 0),
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate from CFDIs if no iva_mensual record
|
||||
const [calcResult] = await prisma.$queryRawUnsafe<[{
|
||||
trasladado: number;
|
||||
acreditable: number;
|
||||
retenido: number;
|
||||
}]>(`
|
||||
SELECT
|
||||
COALESCE(SUM(CASE WHEN tipo = 'ingreso' THEN iva ELSE 0 END), 0) as trasladado,
|
||||
COALESCE(SUM(CASE WHEN tipo = 'egreso' THEN iva ELSE 0 END), 0) as acreditable,
|
||||
COALESCE(SUM(iva_retenido), 0) as retenido
|
||||
FROM "${schema}".cfdis
|
||||
WHERE estado = 'vigente'
|
||||
AND EXTRACT(YEAR FROM fecha_emision) = $1
|
||||
AND EXTRACT(MONTH FROM fecha_emision) = $2
|
||||
`, año, mes);
|
||||
|
||||
const trasladado = Number(calcResult?.trasladado || 0);
|
||||
const acreditable = Number(calcResult?.acreditable || 0);
|
||||
const retenido = Number(calcResult?.retenido || 0);
|
||||
const resultado = trasladado - acreditable - retenido;
|
||||
|
||||
return {
|
||||
trasladado,
|
||||
acreditable,
|
||||
retenido,
|
||||
resultado,
|
||||
acumuladoAnual: resultado,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getIsrMensual(schema: string, año: number): Promise<IsrMensual[]> {
|
||||
// Check if isr_mensual table exists
|
||||
try {
|
||||
const data = await prisma.$queryRawUnsafe<IsrMensual[]>(`
|
||||
SELECT
|
||||
id, año, mes,
|
||||
ingresos_acumulados as "ingresosAcumulados",
|
||||
deducciones,
|
||||
base_gravable as "baseGravable",
|
||||
isr_causado as "isrCausado",
|
||||
isr_retenido as "isrRetenido",
|
||||
isr_a_pagar as "isrAPagar",
|
||||
estado,
|
||||
fecha_declaracion as "fechaDeclaracion"
|
||||
FROM "${schema}".isr_mensual
|
||||
WHERE año = $1
|
||||
ORDER BY mes
|
||||
`, año);
|
||||
|
||||
return data.map(row => ({
|
||||
...row,
|
||||
ingresosAcumulados: Number(row.ingresosAcumulados),
|
||||
deducciones: Number(row.deducciones),
|
||||
baseGravable: Number(row.baseGravable),
|
||||
isrCausado: Number(row.isrCausado),
|
||||
isrRetenido: Number(row.isrRetenido),
|
||||
isrAPagar: Number(row.isrAPagar),
|
||||
}));
|
||||
} catch {
|
||||
// Table doesn't exist, return empty array
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getResumenIsr(schema: string, año: number, mes: number): Promise<ResumenIsr> {
|
||||
// Calculate from CFDIs
|
||||
const [ingresos] = await prisma.$queryRawUnsafe<[{ total: number }]>(`
|
||||
SELECT COALESCE(SUM(total), 0) as total
|
||||
FROM "${schema}".cfdis
|
||||
WHERE tipo = 'ingreso' AND estado = 'vigente'
|
||||
AND EXTRACT(YEAR FROM fecha_emision) = $1
|
||||
AND EXTRACT(MONTH FROM fecha_emision) <= $2
|
||||
`, año, mes);
|
||||
|
||||
const [egresos] = await prisma.$queryRawUnsafe<[{ total: number }]>(`
|
||||
SELECT COALESCE(SUM(total), 0) as total
|
||||
FROM "${schema}".cfdis
|
||||
WHERE tipo = 'egreso' AND estado = 'vigente'
|
||||
AND EXTRACT(YEAR FROM fecha_emision) = $1
|
||||
AND EXTRACT(MONTH FROM fecha_emision) <= $2
|
||||
`, año, mes);
|
||||
|
||||
const [retenido] = await prisma.$queryRawUnsafe<[{ total: number }]>(`
|
||||
SELECT COALESCE(SUM(isr_retenido), 0) as total
|
||||
FROM "${schema}".cfdis
|
||||
WHERE estado = 'vigente'
|
||||
AND EXTRACT(YEAR FROM fecha_emision) = $1
|
||||
AND EXTRACT(MONTH FROM fecha_emision) <= $2
|
||||
`, año, mes);
|
||||
|
||||
const ingresosAcumulados = Number(ingresos?.total || 0);
|
||||
const deducciones = Number(egresos?.total || 0);
|
||||
const baseGravable = Math.max(0, ingresosAcumulados - deducciones);
|
||||
|
||||
// Simplified ISR calculation (actual calculation would use SAT tables)
|
||||
const isrCausado = baseGravable * 0.30; // 30% simplified rate
|
||||
const isrRetenido = Number(retenido?.total || 0);
|
||||
const isrAPagar = Math.max(0, isrCausado - isrRetenido);
|
||||
|
||||
return {
|
||||
ingresosAcumulados,
|
||||
deducciones,
|
||||
baseGravable,
|
||||
isrCausado,
|
||||
isrRetenido,
|
||||
isrAPagar,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user