Commit 757f524f by Vanesa Puma Yamo

Merge remote-tracking branch 'origin/master'

parents 574e9a9f ab0780f9
...@@ -9,7 +9,7 @@ de retenciones mensuales y cálculo de la proyección anual por trabajador. ...@@ -9,7 +9,7 @@ de retenciones mensuales y cálculo de la proyección anual por trabajador.
- Spring Data JPA (Hibernate 7) + PostgreSQL - Spring Data JPA (Hibernate 7) + PostgreSQL
- springdoc-openapi → Swagger UI en `/docs` - springdoc-openapi → Swagger UI en `/docs`
- Lombok (`@Getter/@Setter` en la entity) - Lombok (`@Getter/@Setter` en la entity)
- Tests: JUnit 5 + Testcontainers (PostgreSQL) - **No se escriben tests.** No sugerir ni generar tests salvo pedido explícito.
## Configuración ## Configuración
...@@ -31,14 +31,9 @@ poner `update` ni `create`. ...@@ -31,14 +31,9 @@ poner `update` ni `create`.
```bash ```bash
./mvnw spring-boot:run # levanta en :8080 ./mvnw spring-boot:run # levanta en :8080
./mvnw test # unitarios (no requieren Docker)
./mvnw verify # + tests de integración (requieren Docker)
./mvnw package # genera el war ./mvnw package # genera el war
``` ```
Los tests `*IT` corren en failsafe y levantan PostgreSQL con Testcontainers usando el DDL real de
`src/test/resources/db/renta_quinta.sql`. Sin Docker, `verify` no los ejecuta.
## Estructura ## Estructura
``` ```
...@@ -59,16 +54,21 @@ so.planilla_be ...@@ -59,16 +54,21 @@ so.planilla_be
| Método | Ruta | Descripción | | Método | Ruta | Descripción |
|---|---|---| |---|---|---|
| GET | `/api/v1/renta-quinta` | Grilla paginada de retenciones, con cálculo | | GET | `/api/v1/renta-quinta/listado` | Grilla paginada de retenciones, con cálculo |
| GET | `/api/v1/renta-quinta/{id}` | Boleta completa por `id_renta_quinta` | | GET | `/api/v1/renta-quinta/{id}` | Boleta completa por `id_renta_quinta` |
| GET | `/api/v1/renta-quinta/detallado` | Detalle completo por `numeroDocumento` + `anio` + `mes` |
| GET | `/api/v1/renta-quinta/boleta` | Boleta por `codper` + `anio` + `mes` | | GET | `/api/v1/renta-quinta/boleta` | Boleta por `codper` + `anio` + `mes` |
| GET | `/api/v1/renta-quinta/historial/{codper}/{anio}` | Meses del trabajador en el periodo | | GET | `/api/v1/renta-quinta/historial/{codper}/{anio}` | Meses del trabajador en el periodo |
`anio` y `mes` son los nombres de parámetro usados en **todos** los endpoints (`listado`,
`detallado`, `boleta`, `historial`) — no usar `periodo`/`mesCorte` en la firma pública aunque
internamente `RentaQuintaFiltroDTO` y `DatosCalculo` sigan llamándolos así.
### Filtros de la grilla ### Filtros de la grilla
`periodo` y `mesCorte` son **obligatorios**; el resto es opcional y, si no se envía, no filtra. `anio` y `mes` son **obligatorios**; el resto es opcional y, si no se envía, no filtra. Todos son
Todos son `Integer` salvo `documentoOApellidos`, que es texto (busca por `numero_documento` o por `Integer` salvo `documentoOApellidos`, que es texto (busca por `numero_documento` o por apellidos
apellidos + nombres, con `LIKE` en mayúsculas). + nombres, con `LIKE` en mayúsculas).
Los filtros de catálogo (`sede`, `tipoPlanilla`, `areaGeneral`, `subArea`, `cargo`) llegan como Los filtros de catálogo (`sede`, `tipoPlanilla`, `areaGeneral`, `subArea`, `cargo`) llegan como
número y se comparan **como texto** contra columnas TEXT: el service los convierte con número y se comparan **como texto** contra columnas TEXT: el service los convierte con
......
# Parámetros de los endpoints — renta de quinta categoría
Solo documenta parámetros de entrada (query/path). Las respuestas no cambiaron de estructura.
Base: `/api/v1/renta-quinta`
`anio` y `mes` son los nombres usados en **todos** los endpoints — antes `/listado` usaba
`periodo`/`mesCorte`, ahora unificado.
## GET `/listado`
Grilla paginada de retenciones, con cálculo.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `anio` | Integer | sí | Periodo (año) de la grilla |
| `mes` | Integer (1-12) | sí | Mes de corte |
| `sede` | Integer | no | Filtro de catálogo (comparado como texto) |
| `tipoPlanilla` | Integer | no | Filtro de catálogo (comparado como texto) |
| `areaGeneral` | Integer | no | Filtro de catálogo (comparado como texto) |
| `subArea` | Integer | no | Filtro de catálogo (comparado como texto) |
| `cargo` | Integer | no | Filtro de catálogo (comparado como texto) |
| `documentoOApellidos` | String | no | `numero_documento` o apellidos + nombres, `LIKE` en mayúsculas |
| `page` | Integer | no (default 0) | Página |
| `size` | Integer | no (default 20, tope 100) | Tamaño de página |
## GET `/{id}`
Boleta completa por `id_renta_quinta`.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `id` (path) | Long | sí | `id_renta_quinta` |
## GET `/detallado`
Detalle completo del cálculo (boleta del mes + historial de ingresos, parámetros SUNAT,
liquidación anual, escala por tramos y control de retenciones).
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `numeroDocumento` | String | sí | Documento del trabajador |
| `anio` | Integer | sí | Periodo (año) |
| `mes` | Integer (1-12) | sí | Mes de corte |
## GET `/boleta`
Boleta puntual por `codper` + `anio` + `mes`.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `codper` | Integer | sí | Código de personal |
| `anio` | Integer | sí | Periodo (año) |
| `mes` | Integer (1-12) | sí | Mes de la boleta |
## GET `/historial/{codper}/{anio}`
Meses del trabajador en el periodo.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `codper` (path) | Integer | sí | Código de personal |
| `anio` (path) | Integer | sí | Periodo (año) |
...@@ -8,6 +8,8 @@ public record CalculoRentaQuinta( ...@@ -8,6 +8,8 @@ public record CalculoRentaQuinta(
BigDecimal proyeccionFutura, BigDecimal proyeccionFutura,
BigDecimal proyeccionSueldoBruto, BigDecimal proyeccionSueldoBruto,
BigDecimal proyeccionGratificaciones, BigDecimal proyeccionGratificaciones,
BigDecimal estimadoMesCorteSueldoBruto,
BigDecimal estimadoMesCorteGratificacion,
BigDecimal rentaBrutaAnual, BigDecimal rentaBrutaAnual,
BigDecimal rentaNeta, BigDecimal rentaNeta,
BigDecimal impuestoAnual, BigDecimal impuestoAnual,
......
...@@ -45,13 +45,34 @@ public class RentaQuintaCalculadora { ...@@ -45,13 +45,34 @@ public class RentaQuintaCalculadora {
public CalculoRentaQuinta calcular(DatosCalculo datos) { public CalculoRentaQuinta calcular(DatosCalculo datos) {
BigDecimal valorUit = uit.valorPara(datos.periodo()); BigDecimal valorUit = uit.valorPara(datos.periodo());
BigDecimal proyeccionSueldoBruto = orZero(datos.remuneracionMesCorte()) boolean mesCorteReal = datos.mesReferencia() == datos.mesCorte();
// Si el mes de corte no tiene boleta real (ej. julio sin cargar), se estima con el
// sueldo bruto del mes de referencia y, si el mes de corte es julio o diciembre, su
// gratificación (109% del sueldo bruto). Este estimado reemplaza a planillaCargada (que
// queda en 0 por no ser dato real) en la renta bruta anual.
BigDecimal estimadoSueldoBruto = BigDecimal.ZERO;
BigDecimal estimadoGratificacion = BigDecimal.ZERO;
if (!mesCorteReal) {
estimadoSueldoBruto = orZero(datos.remuneracionReferencia());
if (datos.mesCorte() == 7 || datos.mesCorte() == 12) {
estimadoGratificacion = estimadoSueldoBruto.multiply(FACTOR_GRATIFICACION);
}
}
BigDecimal estimadoMesCorte = estimadoSueldoBruto.add(estimadoGratificacion);
// Meses después del mes de corte: el propio mes de corte ya está cubierto por
// planillaCargada (real) o por el estimado de arriba.
BigDecimal proyeccionSueldoBruto = orZero(datos.remuneracionReferencia())
.multiply(BigDecimal.valueOf(12 - datos.mesCorte())); .multiply(BigDecimal.valueOf(12 - datos.mesCorte()));
BigDecimal proyeccionGratificaciones = gratificacionesPendientes(datos); boolean julioCubierto = datos.julioPercibida() || (datos.mesCorte() == 7 && !mesCorteReal);
boolean diciembreCubierto = datos.diciembrePercibida() || (datos.mesCorte() == 12 && !mesCorteReal);
BigDecimal proyeccionGratificaciones = gratificacionesPendientes(datos, julioCubierto, diciembreCubierto);
BigDecimal proyeccionFutura = proyeccionSueldoBruto.add(proyeccionGratificaciones); BigDecimal proyeccionFutura = proyeccionSueldoBruto.add(proyeccionGratificaciones);
BigDecimal rentaBrutaAnual = orZero(datos.historialReal()) BigDecimal rentaBrutaAnual = orZero(datos.historialReal())
.add(orZero(datos.planillaCargada())) .add(orZero(datos.planillaCargada()))
.add(estimadoMesCorte)
.add(proyeccionFutura); .add(proyeccionFutura);
BigDecimal rentaNeta = rentaBrutaAnual.subtract(DEDUCCION_UIT.multiply(valorUit)); BigDecimal rentaNeta = rentaBrutaAnual.subtract(DEDUCCION_UIT.multiply(valorUit));
...@@ -67,6 +88,8 @@ public class RentaQuintaCalculadora { ...@@ -67,6 +88,8 @@ public class RentaQuintaCalculadora {
escala(proyeccionFutura), escala(proyeccionFutura),
escala(proyeccionSueldoBruto), escala(proyeccionSueldoBruto),
escala(proyeccionGratificaciones), escala(proyeccionGratificaciones),
escala(estimadoSueldoBruto),
escala(estimadoGratificacion),
escala(rentaBrutaAnual), escala(rentaBrutaAnual),
escala(rentaNeta), escala(rentaNeta),
escala(impuestoAnual), escala(impuestoAnual),
...@@ -76,16 +99,18 @@ public class RentaQuintaCalculadora { ...@@ -76,16 +99,18 @@ public class RentaQuintaCalculadora {
tramos); tramos);
} }
/** Gratificaciones de julio y/o diciembre que aún no se han percibido al mes de corte. */ /**
private BigDecimal gratificacionesPendientes(DatosCalculo datos) { * Gratificaciones de julio y/o diciembre que aún no se han percibido por ningún medio (ni
BigDecimal gratificacion = orZero(datos.basico()) * boleta real ni el estimado del propio mes de corte cuando este es julio/diciembre).
.add(orZero(datos.asignacionFamiliar())) */
private BigDecimal gratificacionesPendientes(DatosCalculo datos, boolean julioCubierto, boolean diciembreCubierto) {
BigDecimal gratificacion = orZero(datos.remuneracionReferencia())
.multiply(FACTOR_GRATIFICACION); .multiply(FACTOR_GRATIFICACION);
BigDecimal total = BigDecimal.ZERO; BigDecimal total = BigDecimal.ZERO;
if (datos.mesCorte() < 7) { if (!julioCubierto) {
total = total.add(gratificacion); total = total.add(gratificacion);
} }
if (datos.mesCorte() < 12) { if (!diciembreCubierto) {
total = total.add(gratificacion); total = total.add(gratificacion);
} }
return total; return total;
...@@ -153,15 +178,25 @@ public class RentaQuintaCalculadora { ...@@ -153,15 +178,25 @@ public class RentaQuintaCalculadora {
private record Tramo(String descripcion, BigDecimal limiteUit, BigDecimal tasa) {} private record Tramo(String descripcion, BigDecimal limiteUit, BigDecimal tasa) {}
/** Insumos del cálculo, tomados del acumulado anual del trabajador. */ /**
* Insumos del cálculo, tomados del acumulado anual del trabajador.
*
* <p>{@code mesReferencia} es el último mes con boleta real (<= mesCorte); coincide con
* {@code mesCorte} cuando ese mes sí está cargado. {@code remuneracionReferencia},
* {@code basicoReferencia} y {@code asignacionFamiliarReferencia} vienen de ese mes, y son
* la base de la proyección cuando el mes de corte todavía no tiene boleta.
*/
public record DatosCalculo( public record DatosCalculo(
Integer periodo, Integer periodo,
int mesCorte, int mesCorte,
int mesReferencia,
BigDecimal historialReal, BigDecimal historialReal,
BigDecimal planillaCargada, BigDecimal planillaCargada,
BigDecimal remuneracionMesCorte, BigDecimal remuneracionReferencia,
BigDecimal basico, BigDecimal basicoReferencia,
BigDecimal asignacionFamiliar, BigDecimal asignacionFamiliarReferencia,
BigDecimal retenidoAnterior BigDecimal retenidoAnterior,
boolean julioPercibida,
boolean diciembrePercibida
) {} ) {}
} }
...@@ -34,10 +34,10 @@ public class RentaQuintaController { ...@@ -34,10 +34,10 @@ public class RentaQuintaController {
} }
@Operation(summary = "Grilla de proyección y control de quinta categoría (paginada)") @Operation(summary = "Grilla de proyección y control de quinta categoría (paginada)")
@GetMapping @GetMapping("/listado")
public Page<RentaQuintaResumenDTO> buscar( public Page<RentaQuintaResumenDTO> listado(
@RequestParam @NotNull Integer periodo, @RequestParam @NotNull Integer anio,
@RequestParam @NotNull @Min(1) @Max(12) Integer mesCorte, @RequestParam @NotNull @Min(1) @Max(12) Integer mes,
@RequestParam(required = false) Integer sede, @RequestParam(required = false) Integer sede,
@RequestParam(required = false) Integer tipoPlanilla, @RequestParam(required = false) Integer tipoPlanilla,
@RequestParam(required = false) Integer areaGeneral, @RequestParam(required = false) Integer areaGeneral,
...@@ -47,7 +47,7 @@ public class RentaQuintaController { ...@@ -47,7 +47,7 @@ public class RentaQuintaController {
@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "0") Integer page,
@RequestParam(defaultValue = "20") Integer size) { @RequestParam(defaultValue = "20") Integer size) {
return service.buscar(new RentaQuintaFiltroDTO( return service.buscar(new RentaQuintaFiltroDTO(
periodo, mesCorte, sede, tipoPlanilla, areaGeneral, subArea, cargo, anio, mes, sede, tipoPlanilla, areaGeneral, subArea, cargo,
documentoOApellidos, page, size)); documentoOApellidos, page, size));
} }
...@@ -60,8 +60,8 @@ public class RentaQuintaController { ...@@ -60,8 +60,8 @@ public class RentaQuintaController {
@Operation(summary = "Detalle completo del cálculo por número de documento + año + mes " @Operation(summary = "Detalle completo del cálculo por número de documento + año + mes "
+ "(boleta del mes + puntos 1 a 5: historial de ingresos, parámetros SUNAT, " + "(boleta del mes + puntos 1 a 5: historial de ingresos, parámetros SUNAT, "
+ "liquidación anual, escala por tramos y control de retenciones)") + "liquidación anual, escala por tramos y control de retenciones)")
@GetMapping("/detalle-completo") @GetMapping("/detallado")
public RentaQuintaDetalleCompletoDTO obtenerDetalleCompleto( public RentaQuintaDetalleCompletoDTO detallado(
@RequestParam String numeroDocumento, @RequestParam String numeroDocumento,
@RequestParam Integer anio, @RequestParam Integer anio,
@RequestParam @Min(1) @Max(12) Integer mes) { @RequestParam @Min(1) @Max(12) Integer mes) {
......
...@@ -31,7 +31,6 @@ public record RentaQuintaDetalleCompletoDTO( ...@@ -31,7 +31,6 @@ public record RentaQuintaDetalleCompletoDTO(
BigDecimal horasExtras, BigDecimal horasExtras,
BigDecimal gratificacionesOrdinarias, BigDecimal gratificacionesOrdinarias,
BigDecimal utilidades, BigDecimal utilidades,
BigDecimal utilidadesDistribuidas,
BigDecimal otrosEmpleadores, BigDecimal otrosEmpleadores,
BigDecimal totalBrutoRemunerativo BigDecimal totalBrutoRemunerativo
) {} ) {}
......
...@@ -77,17 +77,27 @@ public class RentaQuintaMapper { ...@@ -77,17 +77,27 @@ public class RentaQuintaMapper {
* de ingresos por columna de periodo, parámetros SUNAT, liquidación anual, escala por tramos * de ingresos por columna de periodo, parámetros SUNAT, liquidación anual, escala por tramos
* y control de retenciones. * y control de retenciones.
*/ */
public RentaQuintaDetalleCompletoDTO toDetalleCompleto(RentaQuinta boleta, List<RentaQuinta> historialAnio, public RentaQuintaDetalleCompletoDTO toDetalleCompleto(RentaQuinta boleta, List<RentaQuinta> historialAnio, int mesCorte, CalculoRentaQuinta calculo) {
int mesCorte, CalculoRentaQuinta calculo) {
BloqueIngresosDTO pasado = bloque(historialAnio, r -> r.getBoletaMes() < mesCorte); BloqueIngresosDTO pasado = bloque(historialAnio, r -> r.getBoletaMes() < mesCorte);
BloqueIngresosDTO actual = bloque(historialAnio, r -> r.getBoletaMes().equals(mesCorte)); boolean mesCorteReal = historialAnio.stream().anyMatch(r -> r.getBoletaMes().equals(mesCorte));
// Sin boleta real del mes de corte, se estima con sueldo bruto del mes de referencia
// (+ gratificación 109% si el mes de corte es julio o diciembre).
BloqueIngresosDTO actual = mesCorteReal
? bloque(historialAnio, r -> r.getBoletaMes().equals(mesCorte))
: new BloqueIngresosDTO(
calculo.estimadoMesCorteSueldoBruto(),
BigDecimal.ZERO,
calculo.estimadoMesCorteGratificacion(),
BigDecimal.ZERO,
BigDecimal.ZERO,
calculo.estimadoMesCorteSueldoBruto().add(calculo.estimadoMesCorteGratificacion()));
BloqueIngresosDTO proyeccion = new BloqueIngresosDTO( BloqueIngresosDTO proyeccion = new BloqueIngresosDTO(
calculo.proyeccionSueldoBruto(), calculo.proyeccionSueldoBruto(),
BigDecimal.ZERO, BigDecimal.ZERO,
calculo.proyeccionGratificaciones(), calculo.proyeccionGratificaciones(),
BigDecimal.ZERO, BigDecimal.ZERO,
BigDecimal.ZERO, BigDecimal.ZERO,
BigDecimal.ZERO,
calculo.proyeccionFutura()); calculo.proyeccionFutura());
BigDecimal ingresosAnuales = pasado.totalBrutoRemunerativo() BigDecimal ingresosAnuales = pasado.totalBrutoRemunerativo()
...@@ -181,7 +191,7 @@ public class RentaQuintaMapper { ...@@ -181,7 +191,7 @@ public class RentaQuintaMapper {
utilidades = utilidades.add(orZero(r.getUtilidades())); utilidades = utilidades.add(orZero(r.getUtilidades()));
} }
BigDecimal total = sueldoBruto.add(horasExtras).add(gratificaciones).add(utilidades); BigDecimal total = sueldoBruto.add(horasExtras).add(gratificaciones).add(utilidades);
return new BloqueIngresosDTO(sueldoBruto, horasExtras, gratificaciones, utilidades, BigDecimal.ZERO, BigDecimal.ZERO, total); return new BloqueIngresosDTO(sueldoBruto, horasExtras, gratificaciones, utilidades, BigDecimal.ZERO, total);
} }
private BigDecimal orZero(BigDecimal v) { private BigDecimal orZero(BigDecimal v) {
......
...@@ -74,9 +74,21 @@ public interface RentaQuintaRepository extends JpaRepository<RentaQuinta, Long> ...@@ -74,9 +74,21 @@ public interface RentaQuintaRepository extends JpaRepository<RentaQuinta, Long>
Integer codper, Integer boletaAnio, Integer boletaMes); Integer codper, Integer boletaAnio, Integer boletaMes);
/** /**
* Mes de referencia por trabajador: el último con boleta real hasta el mes de corte
* (inclusive). Coincide con el mes de corte cuando este sí está cargado; si no, cae al mes
* real anterior y ese pasa a ser la base de la proyección (ver RentaQuintaCalculadora).
*/
String MES_REFERENCIA = """
(select max(r2.boletaMes) from RentaQuinta r2
where r2.numeroDocumento = r.numeroDocumento and r2.boletaAnio = r.boletaAnio
and r2.boletaMes <= :mesCorte)
""";
/**
* Agregado anual por trabajador hasta el mes de corte: acumulado de meses previos * Agregado anual por trabajador hasta el mes de corte: acumulado de meses previos
* (historial real / retenido) y valores del propio mes de corte (planilla cargada / * (historial real / retenido), valores del propio mes de corte (planilla cargada /
* retención del mes). * retención del mes, 0 si no está cargado) y valores del mes de referencia (base de
* proyección cuando el mes de corte no tiene boleta real).
*/ */
@Query(value = """ @Query(value = """
select max(r.codper) as codper, select max(r.codper) as codper,
...@@ -90,9 +102,16 @@ public interface RentaQuintaRepository extends JpaRepository<RentaQuinta, Long> ...@@ -90,9 +102,16 @@ public interface RentaQuintaRepository extends JpaRepository<RentaQuinta, Long>
else 0 end) as planillaCargada, else 0 end) as planillaCargada,
sum(case when r.boletaMes < :mesCorte then coalesce(r.renta5taCategoria, 0) else 0 end) as retenidoAnterior, sum(case when r.boletaMes < :mesCorte then coalesce(r.renta5taCategoria, 0) else 0 end) as retenidoAnterior,
sum(case when r.boletaMes = :mesCorte then coalesce(r.renta5taCategoria, 0) else 0 end) as retencionDelMes, sum(case when r.boletaMes = :mesCorte then coalesce(r.renta5taCategoria, 0) else 0 end) as retencionDelMes,
sum(case when r.boletaMes = :mesCorte then coalesce(r.basico, 0) else 0 end) as basicoMesCorte, max(""" + MES_REFERENCIA + """
sum(case when r.boletaMes = :mesCorte then coalesce(r.asignacionFamiliar, 0) else 0 end) as asignacionFamiliarMesCorte, ) as mesReferencia,
sum(case when r.boletaMes = :mesCorte then coalesce(r.sueldoBruto, 0) else 0 end) as sueldoBrutoMesCorte sum(case when r.boletaMes = """ + MES_REFERENCIA + """
then coalesce(r.basico, 0) else 0 end) as basicoMesCorte,
sum(case when r.boletaMes = """ + MES_REFERENCIA + """
then coalesce(r.asignacionFamiliar, 0) else 0 end) as asignacionFamiliarMesCorte,
sum(case when r.boletaMes = """ + MES_REFERENCIA + """
then coalesce(r.sueldoBruto, 0) else 0 end) as sueldoBrutoMesCorte,
max(case when r.boletaMes = 7 then 1 else 0 end) as julioCargado,
max(case when r.boletaMes = 12 then 1 else 0 end) as diciembreCargado
from RentaQuinta r from RentaQuinta r
where r.boletaAnio = :periodo where r.boletaAnio = :periodo
and r.boletaMes <= :mesCorte and r.boletaMes <= :mesCorte
......
...@@ -23,11 +23,20 @@ public interface RentaQuintaResumenProjection { ...@@ -23,11 +23,20 @@ public interface RentaQuintaResumenProjection {
BigDecimal getRetencionDelMes(); BigDecimal getRetencionDelMes();
/** Básico del mes de corte, usado para proyectar gratificaciones. */ /** Último mes con boleta real (<= mes de corte); base de la proyección. */
Integer getMesReferencia();
/** Básico del mes de referencia, usado para proyectar gratificaciones. */
BigDecimal getBasicoMesCorte(); BigDecimal getBasicoMesCorte();
BigDecimal getAsignacionFamiliarMesCorte(); BigDecimal getAsignacionFamiliarMesCorte();
/** Sueldo bruto del mes de corte, base para proyectar los meses restantes del año. */ /** Sueldo bruto del mes de referencia, base para proyectar los meses restantes del año. */
BigDecimal getSueldoBrutoMesCorte(); BigDecimal getSueldoBrutoMesCorte();
/** 1 si existe boleta real de julio (<= mes de corte), 0 si no. */
Integer getJulioCargado();
/** 1 si existe boleta real de diciembre (<= mes de corte), 0 si no. */
Integer getDiciembreCargado();
} }
...@@ -21,6 +21,7 @@ import so.planilla_be.rentaquinta.repository.RentaQuintaResumenProjection; ...@@ -21,6 +21,7 @@ import so.planilla_be.rentaquinta.repository.RentaQuintaResumenProjection;
import so.planilla_be.rentaquinta.service.RentaQuintaService; import so.planilla_be.rentaquinta.service.RentaQuintaService;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
...@@ -62,12 +63,15 @@ public class RentaQuintaServiceImpl implements RentaQuintaService { ...@@ -62,12 +63,15 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
return calculadora.calcular(new DatosCalculo( return calculadora.calcular(new DatosCalculo(
filtro.periodo(), filtro.periodo(),
filtro.mesCorte(), filtro.mesCorte(),
p.getMesReferencia(),
p.getHistorialReal(), p.getHistorialReal(),
p.getPlanillaCargada(), p.getPlanillaCargada(),
p.getSueldoBrutoMesCorte(), p.getSueldoBrutoMesCorte(),
p.getBasicoMesCorte(), p.getBasicoMesCorte(),
p.getAsignacionFamiliarMesCorte(), p.getAsignacionFamiliarMesCorte(),
p.getRetenidoAnterior())); p.getRetenidoAnterior(),
p.getJulioCargado() != null && p.getJulioCargado() == 1,
p.getDiciembreCargado() != null && p.getDiciembreCargado() == 1));
} }
/** Los catálogos llegan como Integer pero viven en columnas TEXT: se comparan como texto. */ /** Los catálogos llegan como Integer pero viven en columnas TEXT: se comparan como texto. */
...@@ -107,30 +111,44 @@ public class RentaQuintaServiceImpl implements RentaQuintaService { ...@@ -107,30 +111,44 @@ public class RentaQuintaServiceImpl implements RentaQuintaService {
@Override @Override
public RentaQuintaDetalleCompletoDTO obtenerDetalleCompleto(String numeroDocumento, Integer anio, Integer mes) { public RentaQuintaDetalleCompletoDTO obtenerDetalleCompleto(String numeroDocumento, Integer anio, Integer mes) {
List<RentaQuinta> historialAnio = repository.historialAnualPorDocumento(numeroDocumento, anio); List<RentaQuinta> historialAnio = repository.historialAnualPorDocumento(numeroDocumento, anio);
RentaQuinta boleta = historialAnio.stream()
.filter(r -> r.getBoletaMes().equals(mes))
.findFirst()
.orElseThrow(() -> new RentaQuintaNoEncontradaException(
"No existe boleta para documento %s, periodo %d-%02d".formatted(numeroDocumento, anio, mes)));
int mesCorte = mes; int mesCorte = mes;
// Si el mes de corte no tiene boleta real (ej. julio sin cargar), se usa como referencia
// el mes real más reciente anterior — misma regla que la grilla (mesReferencia).
RentaQuinta boletaMesCorte = historialAnio.stream()
.filter(r -> r.getBoletaMes().equals(mesCorte))
.findFirst()
.orElse(null);
RentaQuinta referencia = boletaMesCorte != null ? boletaMesCorte
: historialAnio.stream()
.filter(r -> r.getBoletaMes() < mesCorte)
.max(Comparator.comparing(RentaQuinta::getBoletaMes))
.orElseThrow(() -> new RentaQuintaNoEncontradaException(
"No existe boleta para documento %s, periodo %d-%02d".formatted(numeroDocumento, anio, mes)));
int mesReferencia = referencia.getBoletaMes();
BigDecimal historialReal = mapper.totalBrutoMes(historialAnio, r -> r.getBoletaMes() < mesCorte); BigDecimal historialReal = mapper.totalBrutoMes(historialAnio, r -> r.getBoletaMes() < mesCorte);
BigDecimal planillaCargada = mapper.totalBrutoMes(historialAnio, r -> r.getBoletaMes().equals(mesCorte)); BigDecimal planillaCargada = boletaMesCorte == null ? BigDecimal.ZERO
: mapper.totalBrutoMes(historialAnio, r -> r.getBoletaMes().equals(mesCorte));
BigDecimal retenidoAnterior = sumaMesCorte(historialAnio, r -> r.getBoletaMes() < mesCorte, BigDecimal retenidoAnterior = sumaMesCorte(historialAnio, r -> r.getBoletaMes() < mesCorte,
RentaQuinta::getRenta5taCategoria); RentaQuinta::getRenta5taCategoria);
// La grilla agrega todas las filas del documento en el mes de corte (una persona puede // La grilla agrega todas las filas del documento en el mes de referencia (una persona
// tener varios codper/tipo de planilla); la base de proyección aquí debe hacer lo mismo. // puede tener varios codper/tipo de planilla); la base de proyección aquí hace lo mismo.
BigDecimal sueldoBrutoMesCorte = sumaMesCorte(historialAnio, esMesCorte(mesCorte), RentaQuinta::getSueldoBruto); BigDecimal sueldoBrutoReferencia = sumaMesCorte(historialAnio, esMesCorte(mesReferencia), RentaQuinta::getSueldoBruto);
BigDecimal basicoMesCorte = sumaMesCorte(historialAnio, esMesCorte(mesCorte), RentaQuinta::getBasico); BigDecimal basicoReferencia = sumaMesCorte(historialAnio, esMesCorte(mesReferencia), RentaQuinta::getBasico);
BigDecimal asignacionFamiliarMesCorte = BigDecimal asignacionFamiliarReferencia =
sumaMesCorte(historialAnio, esMesCorte(mesCorte), RentaQuinta::getAsignacionFamiliar); sumaMesCorte(historialAnio, esMesCorte(mesReferencia), RentaQuinta::getAsignacionFamiliar);
boolean julioPercibida = historialAnio.stream().anyMatch(r -> r.getBoletaMes() == 7 && r.getBoletaMes() <= mesCorte);
boolean diciembrePercibida = historialAnio.stream().anyMatch(r -> r.getBoletaMes() == 12 && r.getBoletaMes() <= mesCorte);
CalculoRentaQuinta calculo = calculadora.calcular(new DatosCalculo( CalculoRentaQuinta calculo = calculadora.calcular(new DatosCalculo(
anio, mesCorte, historialReal, planillaCargada, anio, mesCorte, mesReferencia, historialReal, planillaCargada,
sueldoBrutoMesCorte, basicoMesCorte, asignacionFamiliarMesCorte, retenidoAnterior)); sueldoBrutoReferencia, basicoReferencia, asignacionFamiliarReferencia, retenidoAnterior,
julioPercibida, diciembrePercibida));
return mapper.toDetalleCompleto(boleta, historialAnio, mesCorte, calculo); return mapper.toDetalleCompleto(referencia, historialAnio, mesCorte, calculo);
} }
private Predicate<RentaQuinta> esMesCorte(int mesCorte) { private Predicate<RentaQuinta> esMesCorte(int mesCorte) {
......
package so.planilla_be;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import so.planilla_be.rentaquinta.AbstractPostgresIT;
@SpringBootTest
class PlanillaBeApplicationIT extends AbstractPostgresIT {
@Test
void contextLoads() {
}
}
package so.planilla_be.rentaquinta;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
/** Base para tests de integración: levanta PostgreSQL con el DDL real de renta_quinta. */
@Testcontainers
public abstract class AbstractPostgresIT {
@Container
@ServiceConnection
static final PostgreSQLContainer<?> POSTGRES =
new PostgreSQLContainer<>("postgres:16-alpine")
.withInitScript("db/renta_quinta.sql");
}
package so.planilla_be.rentaquinta;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.test.web.servlet.MockMvc;
import so.planilla_be.rentaquinta.repository.RentaQuintaRepository;
import java.util.List;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class RentaQuintaControllerIT extends AbstractPostgresIT {
@Autowired
private MockMvc mockMvc;
@Autowired
private RentaQuintaRepository repository;
@BeforeEach
void setUp() {
repository.deleteAll();
repository.saveAll(List.of(
RentaQuintaTestData.boleta(1001, "40669929", "AQUINO", 2026, 11, "47550.00", "1040.84"),
RentaQuintaTestData.boleta(1001, "40669929", "AQUINO", 2026, 12, "4000.00", "199.16"),
RentaQuintaTestData.boleta(2002, "45778812", "RAMIREZ", 2026, 12, "2800.00", "0.00")));
}
@Test
void grillaDevuelveLasColumnasDeLaTabla() throws Exception {
mockMvc.perform(get("/api/v1/renta-quinta")
.param("periodo", "2026")
.param("mesCorte", "12")
.param("documentoOApellidos", "aquino lopez"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.totalElements").value(1))
.andExpect(jsonPath("$.content[0].numeroDocumento").value("40669929"))
.andExpect(jsonPath("$.content[0].nombreCompleto").value("AQUINO LOPEZ ELIZABETH"))
.andExpect(jsonPath("$.content[0].mesProceso").value(12))
.andExpect(jsonPath("$.content[0].historialReal").value(47550.00))
.andExpect(jsonPath("$.content[0].planillaCargada").value(4000.00))
.andExpect(jsonPath("$.content[0].proyeccionFutura").value(0))
.andExpect(jsonPath("$.content[0].rentaBrutaAnual").value(51550.00))
.andExpect(jsonPath("$.content[0].impuestoAnual").doesNotExist())
.andExpect(jsonPath("$.content[0].retenidoAnterior").value(1040.84))
.andExpect(jsonPath("$.content[0].retencionDelMes").value(199.16));
}
@Test
void grillaLimitaSizeA100() throws Exception {
mockMvc.perform(get("/api/v1/renta-quinta")
.param("periodo", "2026").param("mesCorte", "12").param("size", "5000"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.size").value(100));
}
@Test
void grillaFiltraPorCatalogosNumericos() throws Exception {
mockMvc.perform(get("/api/v1/renta-quinta")
.param("periodo", "2026").param("mesCorte", "12")
.param("sede", "2").param("tipoPlanilla", "1")
.param("areaGeneral", "3").param("subArea", "4").param("cargo", "5"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.totalElements").value(2));
}
@Test
void obtenerBoletaInexistenteDevuelve404() throws Exception {
mockMvc.perform(get("/api/v1/renta-quinta/boleta")
.param("codper", "9999").param("anio", "2026").param("mes", "1"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.status").value(404));
}
@Test
void verHistorialDevuelveMesesConDetalle() throws Exception {
mockMvc.perform(get("/api/v1/renta-quinta/historial/1001/2026"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].boletaMes").value(11))
.andExpect(jsonPath("$[0].remBruta").value(47550.00))
.andExpect(jsonPath("$[0].renta5taCategoria").value(1040.84))
.andExpect(jsonPath("$[1].boletaMes").value(12));
}
}
package so.planilla_be.rentaquinta;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import so.planilla_be.rentaquinta.entity.RentaQuinta;
import so.planilla_be.rentaquinta.repository.RentaQuintaRepository;
import so.planilla_be.rentaquinta.repository.RentaQuintaResumenProjection;
import java.math.BigDecimal;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
class RentaQuintaRepositoryIT extends AbstractPostgresIT {
@Autowired
private RentaQuintaRepository repository;
@BeforeEach
void setUp() {
repository.deleteAll();
repository.saveAll(List.of(
RentaQuintaTestData.boleta(1001, "40669929", "AQUINO", 2026, 11, "47550.00", "1040.84"),
RentaQuintaTestData.boleta(1001, "40669929", "AQUINO", 2026, 12, "4000.00", "199.16"),
RentaQuintaTestData.boleta(2002, "45778812", "RAMIREZ", 2026, 12, "2800.00", "0.00")));
}
@Test
void resumenAcumulaMesesPreviosYSeparaMesDeCorte() {
Page<RentaQuintaResumenProjection> page = repository.buscarResumen(
2026, 12, null, null, null, null, null, null, PageRequest.of(0, 20));
assertThat(page.getTotalElements()).isEqualTo(2);
RentaQuintaResumenProjection aquino = page.getContent().getFirst();
assertThat(aquino.getCodper()).isEqualTo(1001);
assertThat(aquino.getHistorialReal()).isEqualByComparingTo(new BigDecimal("47550.00"));
assertThat(aquino.getPlanillaCargada()).isEqualByComparingTo(new BigDecimal("4000.00"));
assertThat(aquino.getRetenidoAnterior()).isEqualByComparingTo(new BigDecimal("1040.84"));
assertThat(aquino.getRetencionDelMes()).isEqualByComparingTo(new BigDecimal("199.16"));
}
@Test
void resumenFiltraPorTextoDeApellidos() {
Page<RentaQuintaResumenProjection> page = repository.buscarResumen(
2026, 12, null, null, null, null, null, "%AQUINO LOPEZ%", PageRequest.of(0, 20));
assertThat(page.getTotalElements()).isEqualTo(1);
assertThat(page.getContent().getFirst().getNumeroDocumento()).isEqualTo("40669929");
}
@Test
void resumenFiltraPorDocumento() {
Page<RentaQuintaResumenProjection> page = repository.buscarResumen(
2026, 12, null, null, null, null, null, "%45778812%", PageRequest.of(0, 20));
assertThat(page.getTotalElements()).isEqualTo(1);
assertThat(page.getContent().getFirst().getCodper()).isEqualTo(2002);
}
@Test
void resumenFiltraPorCatalogosNumericosGuardadosComoTexto() {
Page<RentaQuintaResumenProjection> conSede = repository.buscarResumen(
2026, 12, "2", "1", "3", "4", "5", null, PageRequest.of(0, 20));
Page<RentaQuintaResumenProjection> sedeInexistente = repository.buscarResumen(
2026, 12, "9", null, null, null, null, null, PageRequest.of(0, 20));
assertThat(conSede.getTotalElements()).isEqualTo(2);
assertThat(sedeInexistente.getTotalElements()).isZero();
}
@Test
void historialAnualDevuelveMesesOrdenados() {
List<RentaQuinta> historial = repository.historialAnual(1001, 2026);
assertThat(historial).extracting(RentaQuinta::getBoletaMes).containsExactly(11, 12);
}
}
package so.planilla_be.rentaquinta;
import so.planilla_be.rentaquinta.entity.RentaQuinta;
import java.math.BigDecimal;
public final class RentaQuintaTestData {
private RentaQuintaTestData() {
}
public static RentaQuinta boleta(int codper, String documento, String apellidoPaterno,
int anio, int mes, String remBruta, String renta5ta) {
RentaQuinta r = new RentaQuinta();
r.setCodper(codper);
r.setNumeroDocumento(documento);
r.setBoletaAnio(anio);
r.setBoletaMes(mes);
r.setBoletaTipoPersonal("1");
r.setCodlocal("2");
r.setAreaGeneral("3");
r.setSubArea("4");
r.setCargo("5");
r.setApellidoPaterno(apellidoPaterno);
r.setApellidoMaterno("LOPEZ");
r.setNombres("ELIZABETH");
r.setSede("LIMA");
r.setSueldoBruto(new BigDecimal(remBruta));
r.setRemBruta(new BigDecimal(remBruta));
r.setRenta5taCategoria(new BigDecimal(renta5ta));
r.setTotalDscto(new BigDecimal("800.00"));
r.setNetoPorPagar(new BigDecimal("4200.00"));
r.setDiasLaborados(30);
return r;
}
}
package so.planilla_be.rentaquinta.calculo;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import so.planilla_be.rentaquinta.calculo.RentaQuintaCalculadora.DatosCalculo;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
class RentaQuintaCalculadoraTest {
private static final BigDecimal UIT_2024 = new BigDecimal("5150");
private final UitCache uitCache = Mockito.mock(UitCache.class);
private final RentaQuintaCalculadora calculadora;
{
Mockito.when(uitCache.valorPara(2024)).thenReturn(UIT_2024);
calculadora = new RentaQuintaCalculadora(uitCache);
}
private DatosCalculo enDiciembre(String historial, String mesActual, String retenidoAnterior) {
return new DatosCalculo(2024, 12, new BigDecimal(historial), new BigDecimal(mesActual),
new BigDecimal(mesActual), BigDecimal.ZERO, BigDecimal.ZERO, new BigDecimal(retenidoAnterior));
}
@Test
void enDiciembreNoProyectaYRetieneElSaldo() {
CalculoRentaQuinta r = calculadora.calcular(enDiciembre("47550.00", "4000.00", "1040.84"));
assertThat(r.proyeccionFutura()).isEqualByComparingTo("0.00");
assertThat(r.rentaBrutaAnual()).isEqualByComparingTo("51550.00");
assertThat(r.impuestoAnual()).isEqualByComparingTo("1240.00");
assertThat(r.retencionDelMes()).isEqualByComparingTo("199.16");
}
@Test
void rentaBajoLas7UitNoPagaImpuesto() {
CalculoRentaQuinta r = calculadora.calcular(enDiciembre("29600.00", "2800.00", "0.00"));
assertThat(r.rentaBrutaAnual()).isEqualByComparingTo("32400.00");
assertThat(r.impuestoAnual()).isEqualByComparingTo("0.00");
assertThat(r.retencionDelMes()).isEqualByComparingTo("0.00");
}
@Test
void aplicaTramosProgresivos() {
// 123,500 - 7 UIT = 87,450 -> 8% de las primeras 5 UIT + 14% del exceso
CalculoRentaQuinta r = calculadora.calcular(enDiciembre("114500.00", "9000.00", "11340.00"));
assertThat(r.impuestoAnual()).isEqualByComparingTo("10698.00");
// Ya se retuvo de más: la retención del mes no puede ser negativa.
assertThat(r.retencionDelMes()).isEqualByComparingTo("0.00");
}
@Test
void proyectaMesesRestantesYGratificaciones() {
// Mayo: faltan 7 meses + gratificación de julio y de diciembre.
DatosCalculo datos = new DatosCalculo(2024, 5, new BigDecimal("16000.00"), new BigDecimal("4000.00"),
new BigDecimal("4000.00"), new BigDecimal("3900.00"), new BigDecimal("100.00"),
new BigDecimal("300.00"));
CalculoRentaQuinta r = calculadora.calcular(datos);
// 4000 * 7 = 28,000 ; gratificaciones (3900+100)*1.09*2 = 8,720
assertThat(r.proyeccionFutura()).isEqualByComparingTo("36720.00");
assertThat(r.rentaBrutaAnual()).isEqualByComparingTo("56720.00");
}
@Test
void usaElDivisorDelMesDeRetencion() {
// Enero: el impuesto anual se reparte en 12.
DatosCalculo enero = new DatosCalculo(2024, 1, BigDecimal.ZERO, new BigDecimal("10000.00"),
new BigDecimal("10000.00"), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO);
CalculoRentaQuinta r = calculadora.calcular(enero);
assertThat(r.rentaBrutaAnual()).isEqualByComparingTo("120000.00");
assertThat(r.retencionDelMes())
.isEqualByComparingTo(r.impuestoAnual().divide(new BigDecimal("12"), 2, java.math.RoundingMode.HALF_UP));
}
}
create table renta_quinta
(
id_renta_quinta bigint generated always as identity
primary key,
boleta_anio integer,
boleta_mes integer,
boleta_tipo_personal char(3),
fecha_registro timestamp default CURRENT_TIMESTAMP,
codper integer,
codlocal char(4),
unidad_organizativa integer,
tipo_documento char(2),
apellido_paterno text,
apellido_materno text,
nombres text,
documento_afp text,
sueldo_bruto numeric(15, 2),
rem_contrato numeric(15, 2),
basico numeric(15, 2),
asignacion_familiar numeric(15, 2),
movilidad numeric(15, 2),
asignacion_estudios numeric(15, 2),
bono_extraordinario numeric(15, 2),
vacaciones_truncas numeric(15, 2),
compensacion_vacacional numeric(15, 2),
vacaciones_gozadas_rem_vaca numeric(15, 2),
cts numeric(15, 2),
gratificacion_proporcional numeric(15, 2),
bonificacion_extraordinaria numeric(15, 2),
gratificacion_julio_diciembre numeric(15, 2),
utilidades numeric(15, 2),
bonificacion_extraordinaria_2 numeric(15, 2),
subsidio_enfermedad numeric(15, 2),
bono_productividad numeric(15, 2),
licencia_goce_haber numeric(15, 2),
subsidio_maternidad numeric(15, 2),
licencia_salud_familiar_ley_30012 numeric(15, 2),
reintegro numeric(15, 2),
horas_extras_25 numeric(15, 2),
horas_extras_35 numeric(15, 2),
apoyo_salud numeric(15, 2),
suma_graciosa numeric(15, 2),
bono_desempeno numeric(15, 2),
asignacion_fallecimiento_familiar numeric(15, 2),
dominical numeric(15, 2),
bono_aniversario numeric(15, 2),
canasta_navidad numeric(15, 2),
vale_consumo numeric(15, 2),
obsequios numeric(15, 2),
rem_bruta numeric(15, 2),
remuneracion_imponible_snp_afp numeric(15, 2),
snp numeric(15, 2),
afp_aportacion_obligatoria numeric(15, 2),
prima_seguro numeric(15, 2),
tipo_comision text,
comision_afp_porcentual numeric(10, 4),
total_afp numeric(15, 2),
renta_5ta_categoria numeric(15, 2),
adelanto numeric(15, 2),
adelanto_vacaciones numeric(15, 2),
retencion_judicial numeric(15, 2),
tardanzas numeric(15, 2),
inasistencias numeric(15, 2),
pension numeric(15, 2),
prestamos numeric(15, 2),
canasta_navidad_2 numeric(15, 2),
vale_consumo_descuento numeric(15, 2),
obsequios_descuento numeric(15, 2),
uniformes_pavitos_navidad_otros numeric(15, 2),
asistencia_tard_fsa_salida_anticipada numeric(15, 2),
uniforme numeric(15, 2),
pavos numeric(15, 2),
colaboracion numeric(15, 2),
seguro_accidente numeric(15, 2),
rifa numeric(15, 2),
otros numeric(15, 2),
dcto_eps numeric(15, 2),
total_dscto numeric(15, 2),
neto_por_pagar numeric(15, 2),
aportes numeric(15, 2),
aporte_essalud_eps numeric(15, 2),
essalud_vida numeric(15, 2),
sctr_salud numeric(15, 2),
sctr_pension numeric(15, 2),
poliza_seguro numeric(15, 2),
total_aportes numeric(15, 2),
numero_documento text,
cussp text,
fecha_nacimiento date,
fecha_ingreso date,
fecha_cese date,
fondo_pension text,
tipo_contrato text,
fecha_inicio_contrato date,
fecha_termino_contrato date,
dias_laborados integer,
dias_no_laborados integer,
dias_subsidiados integer,
descanso_medico integer,
horas_normales integer,
horas_adicionales integer,
area_general text,
sub_area text,
cargo text,
curso text,
observacion text,
sede text,
genero text,
inicio_vacaciones date,
termino_vacaciones date,
lsgh integer,
lcgh integer,
lcgh_fallecimiento integer,
faltas integer,
pago numeric(15, 2),
pendiente numeric(15, 2),
tipo_labor text,
campo_1 integer,
campo_2 integer
);
create index if not exists idx_renta_quinta_codper_anio_mes
on renta_quinta (codper, boleta_anio, boleta_mes);
create table parametro_uit
(
anio integer primary key,
monto_uit numeric(10, 2) not null
);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment