fix: integrar tax_api para despliegue Portainer
CI/CD Pipeline / build (push) Canceled after 0s

This commit is contained in:
ControlMC GitOps
2026-09-11 04:18:36 +00:00
parent a3e9d4c2a8
commit 9b9a8d5ee8
17 changed files with 1978 additions and 4 deletions
+226
View File
@@ -0,0 +1,226 @@
"""
API REST — FastAPI con endpoints de validación y cálculo tributario.
Endpoints:
POST /api/rut/validar — Validar un RUT chileno
POST /api/iva/desde-neto — Calcular IVA desde monto neto
POST /api/iva/desde-bruto — Calcular neto desde monto bruto
POST /api/iva/factura — Desglose de factura con items
POST /api/honorarios/retencion — Calcular retención de honorarios
POST /api/honorarios/liquido — Calcular bruto desde líquido deseado
POST /api/honorarios/anual — Simulación anual de honorarios
POST /api/topes/calcular — Calcular topes imponibles
POST /api/topes/cotizaciones — Calcular cotizaciones previsionales
GET /api/topes/afps — Listar AFPs y tasas
POST /api/f29/simular — Simular declaración F29
Swagger UI disponible en /docs
Redoc disponible en /redoc
Autor: José Nicolás Candia (@mechjook)
"""
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from src.rut import validar_rut
from src.iva import calcular_iva_desde_neto, calcular_neto_desde_bruto, desglose_factura
from src.honorarios import (
calcular_retencion_desde_bruto,
calcular_bruto_desde_liquido,
simular_honorarios_anuales,
)
from src.topes import calcular_topes, calcular_cotizaciones, listar_afps
from src.f29 import simular_f29
# --- App ---
app = FastAPI(
title="API de Validación Tributaria — Chile",
description=(
"Microservicio REST para validación y cálculo tributario chileno. "
"Incluye validación de RUT, cálculo de IVA, retención de honorarios, "
"topes imponibles/cotizaciones y simulador de F29."
),
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc",
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# --- Modelos Pydantic ---
class RutRequest(BaseModel):
rut: str = Field(..., examples=["12.345.678-5"], description="RUT a validar")
class MontoNetoRequest(BaseModel):
monto_neto: float = Field(..., gt=0, examples=[1000000], description="Monto neto (sin IVA)")
tasa: float = Field(default=0.19, ge=0, le=1, description="Tasa de IVA")
class MontoBrutoRequest(BaseModel):
monto_bruto: float = Field(..., gt=0, examples=[1190000], description="Monto bruto (con IVA)")
tasa: float = Field(default=0.19, ge=0, le=1, description="Tasa de IVA")
class FacturaItem(BaseModel):
descripcion: str = Field(..., examples=["Servicio consultoría"])
cantidad: int = Field(..., gt=0, examples=[1])
precio_unitario_neto: float = Field(..., gt=0, examples=[500000])
class FacturaRequest(BaseModel):
items: list[FacturaItem] = Field(..., min_length=1)
class HonorariosBrutoRequest(BaseModel):
monto_bruto: float = Field(..., gt=0, examples=[1000000], description="Monto bruto de la boleta")
anio: int = Field(default=2026, ge=2020, le=2030, description="Año tributario")
class HonorariosLiquidoRequest(BaseModel):
monto_liquido: float = Field(..., gt=0, examples=[847500], description="Monto líquido deseado")
anio: int = Field(default=2026, ge=2020, le=2030, description="Año tributario")
class HonorariosAnualRequest(BaseModel):
monto_mensual_bruto: float = Field(..., gt=0, examples=[1500000])
meses: int = Field(default=12, ge=1, le=12)
anio: int = Field(default=2026, ge=2020, le=2030)
class TopesRequest(BaseModel):
valor_uf: float = Field(default=39842.0, gt=0, description="Valor de la UF en pesos")
class CotizacionesRequest(BaseModel):
renta_bruta: float = Field(..., gt=0, examples=[1500000], description="Renta bruta mensual")
afp: str = Field(default="Habitat", examples=["Habitat"], description="Nombre de la AFP")
tipo_contrato: str = Field(default="indefinido", pattern="^(indefinido|plazo_fijo)$")
valor_uf: float = Field(default=39842.0, gt=0)
class F29Request(BaseModel):
ventas_netas: float = Field(..., ge=0, examples=[10000000])
compras_netas: float = Field(..., ge=0, examples=[6000000])
ventas_exentas: float = Field(default=0, ge=0)
compras_activo_fijo: float = Field(default=0, ge=0)
remanente_anterior: float = Field(default=0, ge=0)
tasa_ppm: float = Field(default=0.01, ge=0, le=0.1)
retenciones_honorarios: float = Field(default=0, ge=0)
# --- Endpoints RUT ---
@app.post("/api/rut/validar", tags=["RUT"])
def api_validar_rut(req: RutRequest):
"""Valida un RUT chileno y retorna su formato correcto."""
return validar_rut(req.rut)
# --- Endpoints IVA ---
@app.post("/api/iva/desde-neto", tags=["IVA"])
def api_iva_desde_neto(req: MontoNetoRequest):
"""Calcula IVA y monto bruto desde un monto neto."""
try:
return calcular_iva_desde_neto(req.monto_neto, req.tasa)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post("/api/iva/desde-bruto", tags=["IVA"])
def api_iva_desde_bruto(req: MontoBrutoRequest):
"""Calcula monto neto e IVA desde un monto bruto (IVA incluido)."""
try:
return calcular_neto_desde_bruto(req.monto_bruto, req.tasa)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post("/api/iva/factura", tags=["IVA"])
def api_desglose_factura(req: FacturaRequest):
"""Genera desglose completo de una factura con múltiples items."""
try:
items = [item.model_dump() for item in req.items]
return desglose_factura(items)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
# --- Endpoints Honorarios ---
@app.post("/api/honorarios/retencion", tags=["Honorarios"])
def api_retencion_honorarios(req: HonorariosBrutoRequest):
"""Calcula retención y líquido desde monto bruto de boleta."""
try:
return calcular_retencion_desde_bruto(req.monto_bruto, req.anio)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post("/api/honorarios/liquido", tags=["Honorarios"])
def api_bruto_desde_liquido(req: HonorariosLiquidoRequest):
"""Calcula monto bruto necesario para recibir un líquido deseado."""
try:
return calcular_bruto_desde_liquido(req.monto_liquido, req.anio)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post("/api/honorarios/anual", tags=["Honorarios"])
def api_honorarios_anuales(req: HonorariosAnualRequest):
"""Simula honorarios anuales con retenciones acumuladas."""
try:
return simular_honorarios_anuales(req.monto_mensual_bruto, req.meses, req.anio)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
# --- Endpoints Topes ---
@app.post("/api/topes/calcular", tags=["Topes Imponibles"])
def api_calcular_topes(req: TopesRequest):
"""Calcula topes imponibles en pesos según valor de UF."""
return calcular_topes(req.valor_uf)
@app.post("/api/topes/cotizaciones", tags=["Topes Imponibles"])
def api_calcular_cotizaciones(req: CotizacionesRequest):
"""Calcula cotizaciones previsionales completas de un trabajador."""
try:
return calcular_cotizaciones(req.renta_bruta, req.afp, req.tipo_contrato, req.valor_uf)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@app.get("/api/topes/afps", tags=["Topes Imponibles"])
def api_listar_afps():
"""Lista todas las AFPs con sus tasas de cotización."""
return listar_afps()
# --- Endpoints F29 ---
@app.post("/api/f29/simular", tags=["F29"])
def api_simular_f29(req: F29Request):
"""Simula la declaración mensual del Formulario 29 (IVA + PPM)."""
try:
return simular_f29(
ventas_netas=req.ventas_netas,
compras_netas=req.compras_netas,
ventas_exentas=req.ventas_exentas,
compras_activo_fijo=req.compras_activo_fijo,
remanente_anterior=req.remanente_anterior,
tasa_ppm=req.tasa_ppm,
retenciones_honorarios=req.retenciones_honorarios,
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
# --- Health ---
@app.get("/api/health", tags=["Sistema"])
def health():
"""Health check del servicio."""
return {"status": "ok", "service": "tax-api", "version": "1.0.0"}