Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
planilla-so-be
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Mauro Paolo Josue Zuñiga Mallqui
planilla-so-be
Commits
757f524f
Commit
757f524f
authored
Sep 10, 2026
by
Vanesa Puma Yamo
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/master'
parents
574e9a9f
ab0780f9
Show whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
212 additions
and
509 deletions
+212
-509
CLAUDE.md
CLAUDE.md
+10
-10
endpoints-parametros.md
docs/endpoints-parametros.md
+63
-0
CalculoRentaQuinta.java
...o/planilla_be/rentaquinta/calculo/CalculoRentaQuinta.java
+2
-0
RentaQuintaCalculadora.java
...anilla_be/rentaquinta/calculo/RentaQuintaCalculadora.java
+48
-13
RentaQuintaController.java
...illa_be/rentaquinta/controller/RentaQuintaController.java
+7
-7
RentaQuintaDetalleCompletoDTO.java
...lla_be/rentaquinta/dto/RentaQuintaDetalleCompletoDTO.java
+0
-1
RentaQuintaMapper.java
.../so/planilla_be/rentaquinta/mapper/RentaQuintaMapper.java
+15
-5
RentaQuintaRepository.java
...illa_be/rentaquinta/repository/RentaQuintaRepository.java
+24
-5
RentaQuintaResumenProjection.java
.../rentaquinta/repository/RentaQuintaResumenProjection.java
+11
-2
RentaQuintaServiceImpl.java
...a_be/rentaquinta/service/impl/RentaQuintaServiceImpl.java
+32
-14
PlanillaBeApplicationIT.java
src/test/java/so/planilla_be/PlanillaBeApplicationIT.java
+0
-14
AbstractPostgresIT.java
...t/java/so/planilla_be/rentaquinta/AbstractPostgresIT.java
+0
-17
RentaQuintaControllerIT.java
...a/so/planilla_be/rentaquinta/RentaQuintaControllerIT.java
+0
-91
RentaQuintaRepositoryIT.java
...a/so/planilla_be/rentaquinta/RentaQuintaRepositoryIT.java
+0
-82
RentaQuintaTestData.java
.../java/so/planilla_be/rentaquinta/RentaQuintaTestData.java
+0
-36
RentaQuintaCalculadoraTest.java
...la_be/rentaquinta/calculo/RentaQuintaCalculadoraTest.java
+0
-83
renta_quinta.sql
src/test/resources/db/renta_quinta.sql
+0
-129
No files found.
CLAUDE.md
View file @
757f524f
...
@@ -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
...
...
docs/endpoints-parametros.md
0 → 100644
View file @
757f524f
# 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) |
src/main/java/so/planilla_be/rentaquinta/calculo/CalculoRentaQuinta.java
View file @
757f524f
...
@@ -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
,
...
...
src/main/java/so/planilla_be/rentaquinta/calculo/RentaQuintaCalculadora.java
View file @
757f524f
...
@@ -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
)
{}
)
{}
}
}
src/main/java/so/planilla_be/rentaquinta/controller/RentaQuintaController.java
View file @
757f524f
...
@@ -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
period
o
,
@RequestParam
@NotNull
Integer
ani
o
,
@RequestParam
@NotNull
@Min
(
1
)
@Max
(
12
)
Integer
mes
Corte
,
@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
(
"/detall
e-complet
o"
)
@GetMapping
(
"/detall
ad
o"
)
public
RentaQuintaDetalleCompletoDTO
obtenerDetalleComplet
o
(
public
RentaQuintaDetalleCompletoDTO
detallad
o
(
@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
)
{
...
...
src/main/java/so/planilla_be/rentaquinta/dto/RentaQuintaDetalleCompletoDTO.java
View file @
757f524f
...
@@ -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
)
{}
)
{}
...
...
src/main/java/so/planilla_be/rentaquinta/mapper/RentaQuintaMapper.java
View file @
757f524f
...
@@ -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
)
{
...
...
src/main/java/so/planilla_be/rentaquinta/repository/RentaQuintaRepository.java
View file @
757f524f
...
@@ -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
...
...
src/main/java/so/planilla_be/rentaquinta/repository/RentaQuintaResumenProjection.java
View file @
757f524f
...
@@ -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
();
}
}
src/main/java/so/planilla_be/rentaquinta/service/impl/RentaQuintaServiceImpl.java
View file @
757f524f
...
@@ -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
()
int
mesCorte
=
mes
;
.
filter
(
r
->
r
.
getBoletaMes
().
equals
(
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
()
.
findFirst
()
.
orElse
(
null
);
RentaQuinta
referencia
=
boletaMesCorte
!=
null
?
boletaMesCorte
:
historialAnio
.
stream
()
.
filter
(
r
->
r
.
getBoletaMes
()
<
mesCorte
)
.
max
(
Comparator
.
comparing
(
RentaQuinta:
:
getBoletaMes
))
.
orElseThrow
(()
->
new
RentaQuintaNoEncontradaException
(
.
orElseThrow
(()
->
new
RentaQuintaNoEncontradaException
(
"No existe boleta para documento %s, periodo %d-%02d"
.
formatted
(
numeroDocumento
,
anio
,
mes
)));
"No existe boleta para documento %s, periodo %d-%02d"
.
formatted
(
numeroDocumento
,
anio
,
mes
)));
int
mes
Corte
=
mes
;
int
mes
Referencia
=
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
(
bolet
a
,
historialAnio
,
mesCorte
,
calculo
);
return
mapper
.
toDetalleCompleto
(
referenci
a
,
historialAnio
,
mesCorte
,
calculo
);
}
}
private
Predicate
<
RentaQuinta
>
esMesCorte
(
int
mesCorte
)
{
private
Predicate
<
RentaQuinta
>
esMesCorte
(
int
mesCorte
)
{
...
...
src/test/java/so/planilla_be/PlanillaBeApplicationIT.java
deleted
100644 → 0
View file @
574e9a9f
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
()
{
}
}
src/test/java/so/planilla_be/rentaquinta/AbstractPostgresIT.java
deleted
100644 → 0
View file @
574e9a9f
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"
);
}
src/test/java/so/planilla_be/rentaquinta/RentaQuintaControllerIT.java
deleted
100644 → 0
View file @
574e9a9f
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
));
}
}
src/test/java/so/planilla_be/rentaquinta/RentaQuintaRepositoryIT.java
deleted
100644 → 0
View file @
574e9a9f
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
);
}
}
src/test/java/so/planilla_be/rentaquinta/RentaQuintaTestData.java
deleted
100644 → 0
View file @
574e9a9f
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
;
}
}
src/test/java/so/planilla_be/rentaquinta/calculo/RentaQuintaCalculadoraTest.java
deleted
100644 → 0
View file @
574e9a9f
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
));
}
}
src/test/resources/db/renta_quinta.sql
deleted
100644 → 0
View file @
574e9a9f
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
);
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment