227 lines
8.2 KiB
Python
227 lines
8.2 KiB
Python
"""
|
|
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"}
|