Compare commits

...

17 Commits

Author SHA1 Message Date
64dc8f4003 Merge branch 'feat/layout' into 'main'
Feat/layout

See merge request jjimenez/erp-imprimelibros!1
2025-09-01 18:35:18 +00:00
7e8d585347 terminado a falta de los calculadores de maquetacion y marcapaginas 2025-09-01 20:34:25 +02:00
f6a2b196e1 arreglado el problema de los acabados 2025-09-01 18:35:08 +02:00
3d9444c3d6 faltan que no se reseteen los acabados de la cubierta 2025-09-01 14:50:42 +02:00
ea1c485c80 trabajando en el resumen de cubierta 2025-08-31 22:06:50 +02:00
6b883ffab2 trabajando en resumen 2025-08-24 21:02:42 +02:00
baf4cb6ae5 terminado acabados cubierta 2025-08-06 14:11:43 +02:00
185088f487 añadidos acabados 2025-08-06 13:28:59 +02:00
a1b8fb01fc movidos los ficheros por funciones 2025-08-06 12:40:08 +02:00
a593a1af78 voy a empezar con los acabados de cubierta 2025-08-06 08:33:37 +02:00
3f89f323cf añadidos los ficheros nuevos2 2025-08-05 10:07:04 +02:00
e7589de194 terminado hasta gramaje de cubierta 2025-08-05 10:06:41 +02:00
29dbd21693 trabajando en la pestaña cubierta 2025-07-31 23:10:28 +02:00
eb0b5610d8 trabajando en interior 2025-07-30 22:18:06 +02:00
8b34d6dca9 añadida validacion en el backend para datos generales 2025-07-28 13:03:34 +02:00
14f6633b83 añadidor los ficheros que faltaban en el commit anterior 2025-07-22 11:37:00 +02:00
3a4e80b1d3 presupuestador html datos generales terminado 2025-07-22 10:57:43 +02:00
113 changed files with 6406 additions and 1986 deletions

6
Dockerfile Normal file
View File

@ -0,0 +1,6 @@
FROM eclipse-temurin:24-jre-alpine
WORKDIR /app
COPY erp*.jar app.jar
EXPOSE 8080
ENV JAVA_OPTS=""
ENTRYPOINT ["sh","-c","java $JAVA_OPTS -jar /app/app.jar"]

43
docker-compose.prod.yml Normal file
View File

@ -0,0 +1,43 @@
version: "3.8"
services:
imprimelibros-db:
image: mysql:8.0
container_name: imprimelibros-db
environment:
MYSQL_ROOT_PASSWORD: NrXz6DK6UoN
MYSQL_DATABASE: imprimelibros
MYSQL_USER: imprimelibros_user
MYSQL_PASSWORD: om91irrDctd
volumes:
- db_data:/var/lib/mysql
networks:
- imprimelibros-network
restart: always
ports:
- "3309:3306" # host:container
imprimelibros-app:
build:
context: .
dockerfile: Dockerfile
image: imprimelibros-app:latest
container_name: imprimelibros-app
depends_on:
- imprimelibros-db
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://imprimelibros-db:3306/imprimelibros?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Europe/Madrid
SPRING_DATASOURCE_USERNAME: imprimelibros_user
SPRING_DATASOURCE_PASSWORD: om91irrDctd
ports:
- "8080:8080"
restart: always
networks:
- imprimelibros-network
volumes:
db_data:
networks:
imprimelibros-network:
driver: bridge

24
docker-compose.yml Normal file
View File

@ -0,0 +1,24 @@
version: '3.8'
services:
imprimelibros-db:
image: mysql:8.0
container_name: imprimelibros-db
environment:
MYSQL_ROOT_PASSWORD: NrXz6DK6UoN
MYSQL_DATABASE: imprimelibros
MYSQL_USER: imprimelibros_user
MYSQL_PASSWORD: om91irrDctd
ports:
- "3309:3306"
volumes:
- db_data:/var/lib/mysql
networks:
- imprimelibros-network
volumes:
db_data:
networks:
imprimelibros-network:
driver: bridge

50
pom.xml
View File

@ -1,39 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.imprimelibros</groupId>
<artifactId>erp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>erp</name>
<description>ERP for Imprime Libros</description>
<url/>
<url />
<licenses>
<license/>
<license />
</licenses>
<developers>
<developer/>
<developer />
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
<connection />
<developerConnection />
<tag />
<url />
</scm>
<properties>
<java.version>24</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
@ -42,36 +39,59 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@ -83,4 +103,4 @@
</plugins>
</build>
</project>
</project>

View File

@ -0,0 +1,65 @@
package com.imprimelibros.erp.config;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@Configuration
public class InternationalizationConfig implements WebMvcConfigurer {
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(Locale.of("es"));
return slr;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang");
registry.addInterceptor(interceptor);
}
@Bean
public MessageSource messageSource() throws IOException {
ReloadableResourceBundleMessageSource source = new ReloadableResourceBundleMessageSource();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:i18n/*.properties");
// Extraer los nombres base sin extensión ni sufijos (_en, _es, etc.)
Set<String> basenames = Arrays.stream(resources)
.map(res -> {
try {
String uri = Objects.requireNonNull(res.getURI()).toString();
// Ej: file:/.../i18n/login_en.properties
String path = uri.substring(uri.indexOf("/i18n/") + 1); // i18n/login_en.properties
String base = path.replaceAll("_[a-z]{2}\\.properties$", "") // login.properties
.replaceAll("\\.properties$", "");
return "classpath:" + base;
} catch (IOException e) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toSet());
source.setBasenames(basenames.toArray(new String[0]));
source.setDefaultEncoding("UTF-8");
return source;
}
}

View File

@ -0,0 +1,36 @@
package com.imprimelibros.erp.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/",
"/assets/**",
"/css/**",
"/js/**",
"/images/**",
"/public/**",
"/error",
"/presupuesto/public/**")
.permitAll()
.anyRequest().authenticated())
.csrf(csrf -> csrf
.ignoringRequestMatchers("/presupuesto/public/**"))
.formLogin(login -> login
// .loginPage("/login") añadir cuando se tenga login personalizado
.permitAll())
.logout(logout -> logout.permitAll());
return http.build();
}
}

View File

@ -0,0 +1,30 @@
package com.imprimelibros.erp.configurationERP;
import jakarta.persistence.*;
@Entity
@Table(name = "variables")
public class Variable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String clave;
private String valor;
// Getters y setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getClave() { return clave; }
public void setClave(String clave) { this.clave = clave; }
public String getValor() { return valor; }
public void setValor(String valor) { this.valor = valor; }
}

View File

@ -0,0 +1,9 @@
package com.imprimelibros.erp.configurationERP;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface VariableRepository extends JpaRepository<Variable, Long> {
Optional<Variable> findByClave(String clave);
}

View File

@ -0,0 +1,19 @@
package com.imprimelibros.erp.configurationERP;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class VariableService {
@Autowired
private VariableRepository variableRepository;
public Integer getValorEntero(String clave) {
return variableRepository.findByClave(clave)
.<Integer>map(v -> Integer.parseInt(v.getValor()))
.orElseThrow(
() -> new IllegalArgumentException("No se encontró la variable con clave '" + clave + "'"));
}
}

View File

@ -0,0 +1,41 @@
package com.imprimelibros.erp.externalApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Service
public class AuthService {
@Value("${safekat.api.url}")
private String skApiUrl;
@Value("${safekat.api.email}")
private String skApiEmail;
@Value("${safekat.api.password}")
private String skApiPassword;
private final RestTemplate restTemplate = new RestTemplate();
private String currentToken;
public synchronized String getToken() {
if (currentToken == null) {
currentToken = fetchNewToken();
}
return currentToken;
}
public synchronized void invalidateToken() {
currentToken = null;
}
private String fetchNewToken() {
Map<String, String> credentials = Map.of(
"email", this.skApiEmail,
"password", this.skApiPassword);
ResponseEntity<Map> response = restTemplate.postForEntity(this.skApiUrl + "auth/jwt", credentials, Map.class);
return (String) response.getBody().get("access_token");
}
}

View File

@ -0,0 +1,165 @@
package com.imprimelibros.erp.externalApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
import java.util.HashMap;
import java.util.function.Supplier;
@Service
public class skApiClient {
@Value("${safekat.api.url}")
private String skApiUrl;
private final AuthService authService;
private final RestTemplate restTemplate;
public skApiClient(AuthService authService) {
this.authService = authService;
this.restTemplate = new RestTemplate();
}
public String getPrice(Map<String, Object> requestBody) {
return performWithRetry(() -> {
String url = this.skApiUrl + "api/calcular";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(authService.getToken());
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.POST,
entity,
String.class);
try {
Map<String, Object> responseBody = new ObjectMapper().readValue(response.getBody(), Map.class);
if (responseBody.get("error") == null) {
return new ObjectMapper().writeValueAsString(
Map.of("data", responseBody.get("data")));
} else {
return "{\"error\": 1}";
}
} catch (JsonProcessingException e) {
e.printStackTrace();
return "{\"error\": 1}";
}
});
}
public Integer getMaxSolapas(Map<String, Object> requestBody) {
try {
String jsonResponse = performWithRetry(() -> {
String url = this.skApiUrl + "api/calcular-solapas";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(authService.getToken()); // token actualizado
Map<String, Object> data = new HashMap<>();
data.put("clienteId", requestBody.get("clienteId"));
data.put("tamanio", requestBody.get("tamanio"));
data.put("tirada", requestBody.get("tirada"));
data.put("paginas", requestBody.get("paginas"));
data.put("paginasColor", requestBody.get("paginasColor"));
data.put("papelInteriorDiferente", 0);
data.put("paginasCuadernillo", requestBody.get("paginasCuadernillo"));
data.put("tipo", requestBody.get("tipo"));
data.put("isColor", requestBody.get("isColor"));
data.put("isHq", requestBody.get("isHq"));
data.put("interior", requestBody.get("interior"));
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(data, headers);
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.POST,
entity,
String.class);
return response.getBody();
});
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jsonResponse);
if (root.get("data") == null || !root.get("data").isInt()) {
throw new RuntimeException("Respuesta inesperada de calcular-solapas: " + jsonResponse);
}
return root.get("data").asInt();
} catch (JsonProcessingException e) {
// Fallback al 80% del ancho
Map<String, Object> tamanio = (Map<String, Object>) requestBody.get("tamanio");
if (tamanio == null || tamanio.get("ancho") == null)
throw new RuntimeException("Tamaño no válido en la solicitud: " + requestBody);
else {
int ancho = (int) tamanio.get("ancho");
return (int) Math.floor(ancho * 0.8); // 80% del ancho
}
}
}
public Double getRetractilado(Map<String, Object> requestBody) {
String value = performWithRetry(() -> {
String url = this.skApiUrl + "api/calcular-retractilado";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(authService.getToken());
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.POST,
entity,
String.class);
try {
Map<String, Object> responseBody = new ObjectMapper().readValue(response.getBody(), Map.class);
return responseBody.get("data").toString();
} catch (JsonProcessingException e) {
e.printStackTrace();
return "0.0"; // Fallback en caso de error
}
});
try {
return Double.parseDouble(value);
} catch (NumberFormatException e) {
throw new RuntimeException("Error al parsear el valor de retractilado: " + value, e);
}
}
/******************
* PRIVATE METHODS
******************/
private String performWithRetry(Supplier<String> request) {
try {
return request.get();
} catch (HttpClientErrorException.Unauthorized e) {
// Token expirado, renovar y reintentar
authService.invalidateToken();
try {
return request.get(); // segundo intento
} catch (HttpClientErrorException ex) {
throw new RuntimeException("La autenticación ha fallado tras renovar el token.", ex);
}
}
}
}

View File

@ -0,0 +1,48 @@
package com.imprimelibros.erp.home;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import com.imprimelibros.erp.configurationERP.VariableService;
import com.imprimelibros.erp.i18n.TranslationService;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@Controller
public class HomeController {
@Autowired
private TranslationService translationService;
@Autowired
private VariableService variableService;
@GetMapping("/")
public String index(Model model, Authentication authentication, Locale locale) {
boolean isAuthenticated = authentication != null && authentication.isAuthenticated()
&& !(authentication instanceof AnonymousAuthenticationToken);
if (!isAuthenticated) {
List<String> keys = List.of(
"presupuesto.plantilla-cubierta",
"presupuesto.plantilla-cubierta-text",
"presupuesto.impresion-cubierta",
"presupuesto.impresion-cubierta-help");
Map<String, String> translations = translationService.getTranslations(locale, keys);
model.addAttribute("languageBundle", translations);
model.addAttribute("pod", variableService.getValorEntero("POD"));
model.addAttribute("ancho_alto_min", variableService.getValorEntero("ancho_alto_min"));
model.addAttribute("ancho_alto_max", variableService.getValorEntero("ancho_alto_max"));
}
return "imprimelibros/home";
}
}

View File

@ -0,0 +1,22 @@
package com.imprimelibros.erp.i18n;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
public class TranslationService {
@Autowired
private MessageSource messageSource;
public Map<String, String> getTranslations(Locale locale, List<String> keys) {
Map<String, String> translations = new HashMap<>();
for (String key : keys) {
translations.put(key, messageSource.getMessage(key, null, locale));
}
return translations;
}
}

View File

@ -0,0 +1,14 @@
package com.imprimelibros.erp.login;
import java.util.Locale;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
public class LoginController {
@GetMapping("/login")
public String index(Model model, Locale locale) {
return "imprimelibros/login/login";
}
}

View File

@ -0,0 +1,516 @@
package com.imprimelibros.erp.presupuesto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import com.imprimelibros.erp.presupuesto.validation.ConsistentTiradas;
import com.imprimelibros.erp.presupuesto.validation.Par;
import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups;
import com.imprimelibros.erp.presupuesto.validation.Tamanio;
import jakarta.persistence.*;
@ConsistentTiradas(groups = PresupuestoValidationGroups.DatosGenerales.class)
@Tamanio(groups = PresupuestoValidationGroups.DatosGenerales.class)
@Entity
@Table(name = "presupuesto")
public class Presupuesto implements Cloneable{
public enum TipoEncuadernacion {
fresado, cosido, grapado, espiral, wireo
}
public enum TipoImpresion {
negro, negrohq, color, colorhq
}
public enum TipoCubierta {
tapaBlanda, tapaDura, tapaDuraLomoRedondo
}
@Override
public Presupuesto clone() {
try {
return (Presupuesto) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull(message = "{presupuesto.errores.tipo-encuadernacion}", groups = PresupuestoValidationGroups.DatosGenerales.class)
@Column(name = "tipo_encuadernacion")
private TipoEncuadernacion tipoEncuadernacion = TipoEncuadernacion.fresado;
@NotBlank(message = "{presupuesto.errores.titulo}", groups = PresupuestoValidationGroups.DatosGenerales.class)
@Column(name = "titulo")
private String titulo;
@Column(name = "autor")
private String autor;
@Column(name = "isbn")
private String isbn;
@NotNull(message = "{presupuesto.errores.tirada1}", groups = PresupuestoValidationGroups.DatosGenerales.class)
@Column(name = "tirada1")
private Integer tirada1;
@Column(name = "tirada2")
private Integer tirada2;
@Column(name = "tirada3")
private Integer tirada3;
@Column(name = "tirada4")
private Integer tirada4;
@Column(name = "selected_tirada")
private Integer selectedTirada;
@NotNull(message = "{presupuesto.errores.ancho}", groups = PresupuestoValidationGroups.DatosGenerales.class)
@Column(name = "ancho")
private Integer ancho;
@NotNull(message = "{presupuesto.errores.alto}", groups = PresupuestoValidationGroups.DatosGenerales.class)
@Column(name = "alto")
private Integer alto;
@Column(name = "formatoPersonalizado")
private Boolean formatoPersonalizado;
@Par(message = "{presupuesto.errores.paginasNegro.par}", groups = PresupuestoValidationGroups.DatosGenerales.class)
@NotNull(message = "{presupuesto.errores.paginasNegro.required}", groups = PresupuestoValidationGroups.DatosGenerales.class)
@Column(name = "paginas_negro")
private Integer paginasNegro;
@Par(message = "{presupuesto.errores.paginasColor.par}", groups = PresupuestoValidationGroups.DatosGenerales.class)
@NotNull(message = "{presupuesto.errores.paginasColor.required}", groups = PresupuestoValidationGroups.DatosGenerales.class)
@Column(name = "paginas_color")
private Integer paginasColor;
@Column(name = "posicion_paginas_color")
private String posicionPaginasColor;
@Column(name = "paginas_color_total")
private Integer paginasColorTotal;
@NotNull(message = "{presupuesto.errores.tipo-impresion}", groups = PresupuestoValidationGroups.Interior.class)
@Column(name = "tipo_impresion")
private TipoImpresion tipoImpresion = TipoImpresion.negro;
@NotNull(message = "{presupuesto.errores.papel-interior}", groups = PresupuestoValidationGroups.Interior.class)
@Column(name = "papel_interior_id")
private Integer papelInteriorId;
@NotNull(message = "{presupuesto.errores.gramaje-interior}", groups = PresupuestoValidationGroups.Interior.class)
@Column(name = "gramaje_interior")
private Integer gramajeInterior;
@NotNull(message = "{presupuesto.errores.tipo-cubierta}", groups = PresupuestoValidationGroups.Cubierta.class)
@Column(name = "tipo_cubierta")
private TipoCubierta tipoCubierta = TipoCubierta.tapaBlanda;
@Column(name = "solapas_cubierta")
private Boolean solapasCubierta = false;
@Column(name = "tamanio_solapas_cubierta")
private Integer tamanioSolapasCubierta;
@Column(name = "cubierta_caras")
private Integer cubiertaCaras;
@Column(name = "papel_guardas_id")
private Integer papelGuardasId;
@Column(name = "gramaje_guardas")
private Integer gramajeGuardas;
@Column(name = "guardas_impresas")
private Integer guardasImpresas;
@Column(name = "cabezada")
private String cabezada;
@NotNull(message = "{presupuesto.errores.papel-cubierta}", groups = PresupuestoValidationGroups.Cubierta.class)
@Column(name = "papel_cubierta_id")
private Integer papelCubiertaId = 2;
@NotNull(message = "{presupuesto.errores.gramaje-cubierta}", groups = PresupuestoValidationGroups.Cubierta.class)
@Column(name = "gramaje_cubierta")
private Integer gramajeCubierta = 240;
@NotNull(message = "{presupuesto.errores.acabado-cubierta}", groups = PresupuestoValidationGroups.Cubierta.class)
@Column(name = "acabado")
private Integer acabado = 1;
@Column(name = "sobrecubierta")
private Boolean sobrecubierta = false;
@Column(name = "papel_sobrecubierta_id")
private Integer papelSobrecubiertaId = 2;
@Column(name = "gramaje_sobrecubierta")
private Integer gramajeSobrecubierta = 170;
@Column(name = "tamanio_solapas_sobrecubierta")
private Integer tamanioSolapasSobrecubierta = 80;
@Column(name = "acabado_sobrecubierta")
private Integer acabadoSobrecubierta = 0; // 0: sin acabado,
@Column(name = "faja")
private Boolean faja = false;
@Column(name = "papel_faja_id")
private Integer papelFajaId = 2;
@Column(name = "gramaje_faja")
private Integer gramajeFaja = 170;
@Column(name = "tamanio_solapas_faja")
private Integer tamanioSolapasFaja = 80;
@Column(name = "acabado_faja")
private Integer acabadoFaja = 0; // 0: sin acabado
@Column(name = "alto_faja")
private Integer altoFaja = 0;
// Getters y Setters
public String getAutor() {
return autor;
}
public void setAutor(String autor) {
this.autor = autor;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public Integer getTirada1() {
return tirada1;
}
public void setTirada1(Integer tirada1) {
this.tirada1 = tirada1;
}
public Integer getTirada2() {
return tirada2;
}
public void setTirada2(Integer tirada2) {
this.tirada2 = tirada2;
}
public Integer getTirada3() {
return tirada3;
}
public void setTirada3(Integer tirada3) {
this.tirada3 = tirada3;
}
public Integer getTirada4() {
return tirada4;
}
public Integer[] getTiradas() {
return new Integer[] { tirada1, tirada2, tirada3, tirada4 };
}
public void setTirada4(Integer tirada4) {
this.tirada4 = tirada4;
}
public Integer getAncho() {
return ancho;
}
public void setAncho(Integer ancho) {
this.ancho = ancho;
}
public Integer getAlto() {
return alto;
}
public void setAlto(Integer alto) {
this.alto = alto;
}
public Boolean getFormatoPersonalizado() {
return formatoPersonalizado;
}
public void setFormatoPersonalizado(Boolean formatoPersonalizado) {
this.formatoPersonalizado = formatoPersonalizado;
}
public String getTitulo() {
return titulo;
}
public void setTitulo(String titulo) {
this.titulo = titulo;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getPaginasNegro() {
return paginasNegro;
}
public void setPaginasNegro(Integer paginasNegro) {
this.paginasNegro = paginasNegro;
}
public Integer getPaginasColor() {
return paginasColor;
}
public void setPaginasColor(Integer paginasColor) {
this.paginasColor = paginasColor;
}
public TipoEncuadernacion getTipoEncuadernacion() {
return tipoEncuadernacion;
}
public void setTipoEncuadernacion(TipoEncuadernacion tipoEncuadernacion) {
this.tipoEncuadernacion = tipoEncuadernacion;
}
public String getPosicionPaginasColor() {
return posicionPaginasColor;
}
public void setPosicionPaginasColor(String posicionPaginasColor) {
this.posicionPaginasColor = posicionPaginasColor;
}
public Integer getPaginasColorTotal() {
return paginasColorTotal;
}
public void setPaginasColorTotal(Integer paginasColorTotal) {
this.paginasColorTotal = paginasColorTotal;
}
public TipoImpresion getTipoImpresion() {
return tipoImpresion;
}
public void setTipoImpresion(TipoImpresion tipoImpresion) {
this.tipoImpresion = tipoImpresion;
}
public Integer getPapelInteriorId() {
return papelInteriorId;
}
public void setPapelInteriorId(Integer papelInteriorId) {
this.papelInteriorId = papelInteriorId;
}
public Integer getGramajeInterior() {
return gramajeInterior;
}
public void setGramajeInterior(Integer gramajeInterior) {
this.gramajeInterior = gramajeInterior;
}
public TipoCubierta getTipoCubierta() {
return tipoCubierta;
}
public void setTipoCubierta(TipoCubierta tipoCubierta) {
this.tipoCubierta = tipoCubierta;
}
public Boolean getSolapasCubierta() {
return solapasCubierta;
}
public void setSolapasCubierta(Boolean solapasCubierta) {
this.solapasCubierta = solapasCubierta;
}
public Integer getTamanioSolapasCubierta() {
return tamanioSolapasCubierta;
}
public void setTamanioSolapasCubierta(Integer tamanioSolapasCubierta) {
this.tamanioSolapasCubierta = tamanioSolapasCubierta;
}
public Integer getCubiertaCaras() {
return cubiertaCaras;
}
public void setCubiertaCaras(Integer cubiertaCaras) {
this.cubiertaCaras = cubiertaCaras;
}
public Integer getPapelGuardasId() {
return papelGuardasId;
}
public void setPapelGuardasId(Integer papelGuardasId) {
this.papelGuardasId = papelGuardasId;
}
public Integer getGramajeGuardas() {
return gramajeGuardas;
}
public void setGramajeGuardas(Integer gramajeGuardas) {
this.gramajeGuardas = gramajeGuardas;
}
public Integer getGuardasImpresas() {
return guardasImpresas;
}
public void setGuardasImpresas(Integer guardasImpresas) {
this.guardasImpresas = guardasImpresas;
}
public String getCabezada() {
return cabezada;
}
public void setCabezada(String cabezada) {
this.cabezada = cabezada;
}
public Integer getPapelCubiertaId() {
return papelCubiertaId;
}
public void setPapelCubiertaId(Integer papelCubiertaId) {
this.papelCubiertaId = papelCubiertaId;
}
public Integer getGramajeCubierta() {
return gramajeCubierta;
}
public void setGramajeCubierta(Integer gramajeCubierta) {
this.gramajeCubierta = gramajeCubierta;
}
public Integer getAcabado() {
return acabado;
}
public void setAcabado(Integer acabado) {
this.acabado = acabado;
}
public Boolean getSobrecubierta() {
return sobrecubierta;
}
public void setSobrecubierta(Boolean sobrecubierta) {
this.sobrecubierta = sobrecubierta;
}
public Integer getPapelSobrecubiertaId() {
return papelSobrecubiertaId;
}
public void setPapelSobrecubiertaId(Integer papelSobrecubiertaId) {
this.papelSobrecubiertaId = papelSobrecubiertaId;
}
public Integer getGramajeSobrecubierta() {
return gramajeSobrecubierta;
}
public void setGramajeSobrecubierta(Integer gramajeSobrecubierta) {
this.gramajeSobrecubierta = gramajeSobrecubierta;
}
public Integer getTamanioSolapasSobrecubierta() {
return tamanioSolapasSobrecubierta;
}
public void setTamanioSolapasSobrecubierta(Integer tamanioSolapasSobrecubierta) {
this.tamanioSolapasSobrecubierta = tamanioSolapasSobrecubierta;
}
public Integer getAcabadoSobrecubierta() {
return acabadoSobrecubierta;
}
public void setAcabadoSobrecubierta(Integer acabadoSobrecubierta) {
this.acabadoSobrecubierta = acabadoSobrecubierta;
}
public Boolean getFaja() {
return faja;
}
public void setFaja(Boolean faja) {
this.faja = faja;
}
public Integer getPapelFajaId() {
return papelFajaId;
}
public void setPapelFajaId(Integer papelFajaId) {
this.papelFajaId = papelFajaId;
}
public Integer getGramajeFaja() {
return gramajeFaja;
}
public void setGramajeFaja(Integer gramajeFaja) {
this.gramajeFaja = gramajeFaja;
}
public Integer getTamanioSolapasFaja() {
return tamanioSolapasFaja;
}
public void setTamanioSolapasFaja(Integer tamanioSolapasFaja) {
this.tamanioSolapasFaja = tamanioSolapasFaja;
}
public Integer getAcabadoFaja() {
return acabadoFaja;
}
public void setAcabadoFaja(Integer acabadoFaja) {
this.acabadoFaja = acabadoFaja;
}
public Integer getAltoFaja() {
return altoFaja;
}
public void setAltoFaja(Integer altoFaja) {
this.altoFaja = altoFaja;
}
public Integer getSelectedTirada() {
return selectedTirada;
}
public void setSelectedTirada(Integer selectedTirada) {
this.selectedTirada = selectedTirada;
}
}

View File

@ -0,0 +1,304 @@
package com.imprimelibros.erp.presupuesto;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.PostMapping;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.imprimelibros.erp.externalApi.skApiClient;
import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto;
import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups;
@RestController
@RequestMapping("/presupuesto")
public class PresupuestoController {
@Autowired
protected PresupuestoService presupuestoService;
@Autowired
protected skApiClient apiClient;
@Autowired
protected MessageSource messageSource;
@PostMapping("/public/validar/datos-generales")
public ResponseEntity<?> validarDatosGenerales(
@Validated(PresupuestoValidationGroups.DatosGenerales.class) Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage()));
// errores globales (@ConsistentTiradas...)
result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage()));
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
// opciones color
Map<String, Object> resultado = presupuestoService.obtenerOpcionesColor(presupuesto, locale);
// opciones papel interior
resultado.putAll(presupuestoService.obtenerOpcionesPapelInterior(presupuesto, locale));
// opciones gramaje interior
resultado.putAll(presupuestoService.obtenerOpcionesGramajeInterior(presupuesto));
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/validar/interior")
public ResponseEntity<?> validarInterior(
@Validated(PresupuestoValidationGroups.Interior.class) Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage()));
// errores globales (@ConsistentTiradas...)
result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage()));
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
Map<String, Object> resultado = new HashMap<>();
resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto)));
resultado.putAll(presupuestoService.obtenerOpcionesAcabadosCubierta(presupuesto, locale));
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/validar/cubierta")
public ResponseEntity<?> validarCubierta(
@Validated(PresupuestoValidationGroups.Cubierta.class) Presupuesto presupuesto,
@RequestParam(name = "calcular", defaultValue = "true") boolean calcular,
BindingResult result, Locale locale) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage()));
// errores globales (@ConsistentTiradas...)
result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage()));
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
if (calcular) {
HashMap<String, Object> price = new HashMap<>();
String priceStr = apiClient.getPrice(presupuestoService.toSkApiRequest(presupuesto));
try {
price = new ObjectMapper().readValue(priceStr, new TypeReference<>() {
});
} catch (JsonProcessingException e) {
price = new HashMap<>();
price.put("error", messageSource.getMessage("presupuesto.error-obtener-precio", null, locale));
}
if (!price.containsKey("data")) {
return ResponseEntity.badRequest()
.body(messageSource.getMessage("presupuesto.error-obtener-precio", null, locale));
}
return ResponseEntity.ok(price.get("data"));
}
return ResponseEntity.ok().build();
}
@PostMapping("/public/validar/seleccion-tirada")
public ResponseEntity<?> validarSeleccionTirada(
Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage()));
// errores globales (@ConsistentTiradas...)
result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage()));
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
Map<String, Object> resultado = new HashMap<>();
// servicios extra
resultado.putAll(presupuestoService.obtenerServiciosExtras(presupuesto, locale, apiClient));
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-papel-interior")
public ResponseEntity<?> getPapelInterior(
@Validated(PresupuestoValidationGroups.Interior.class) Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage()));
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
// opciones color
Map<String, Object> resultado = presupuestoService.obtenerOpcionesPapelInterior(presupuesto, locale);
// opciones gramaje interior
resultado.putAll(presupuestoService.obtenerOpcionesGramajeInterior(presupuesto));
List<String> opciones = (List<String>) resultado.get("opciones_gramaje_interior");
if (opciones != null && !opciones.isEmpty()) {
String gramajeActual = presupuesto.getGramajeInterior().toString();
if (!opciones.contains(gramajeActual)) {
presupuesto.setGramajeInterior(Integer.parseInt(opciones.get(0))); // Asignar primera opción
}
}
resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto)));
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-gramaje-interior")
public ResponseEntity<?> getGramajeInterior(
@Validated(PresupuestoValidationGroups.Interior.class) Presupuesto presupuesto,
BindingResult result) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage()));
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
Map<String, Object> resultado = presupuestoService.obtenerOpcionesGramajeInterior(presupuesto);
List<String> opciones = (List<String>) resultado.get("opciones_gramaje_interior");
if (opciones != null && !opciones.isEmpty()) {
String gramajeActual = presupuesto.getGramajeInterior().toString();
if (!opciones.contains(gramajeActual)) {
presupuesto.setGramajeInterior(Integer.parseInt(opciones.get(0))); // Asignar primera opción
}
}
resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto)));
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-max-solapas")
public ResponseEntity<?> getMaxSolapas(
@Validated(PresupuestoValidationGroups.Interior.class) Presupuesto presupuesto,
BindingResult result) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage()));
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
Map<String, Object> resultado = new HashMap<>();
resultado.put("solapas", apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto)));
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-papel-cubierta")
public ResponseEntity<?> getPapelCubierta(
Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, Object> resultado = new HashMap<>();
Map<String, Object> papelesCubierta = presupuestoService.obtenerOpcionesPapelCubierta(presupuesto, locale);
List<ImagenPresupuesto> opciones = (List<ImagenPresupuesto>) presupuestoService
.obtenerOpcionesPapelCubierta(presupuesto, locale)
.get("opciones_papel_cubierta");
if (opciones != null && opciones.stream().noneMatch(
o -> o.getExtra_data().get("sk-id").equals(String.valueOf(presupuesto.getPapelCubiertaId())))) {
presupuesto.setPapelCubiertaId(Integer.valueOf(opciones.get(0).getExtra_data().get("sk-id")));
}
resultado.putAll(papelesCubierta);
resultado.putAll(presupuestoService.obtenerOpcionesGramajeCubierta(presupuesto));
List<String> gramajesCubierta = (List<String>) resultado.get("opciones_gramaje_cubierta");
if (gramajesCubierta != null && !gramajesCubierta.isEmpty()) {
String gramajeActual = presupuesto.getGramajeCubierta().toString();
if (!gramajesCubierta.contains(gramajeActual)) {
presupuesto.setGramajeCubierta(Integer.parseInt(gramajesCubierta.get(0))); // Asignar primera opción
}
}
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-gramaje-cubierta")
public ResponseEntity<?> getGramajeCubierta(
Presupuesto presupuesto,
BindingResult result) {
Map<String, Object> resultado = presupuestoService.obtenerOpcionesGramajeCubierta(presupuesto);
List<String> gramajesCubierta = (List<String>) resultado.get("opciones_gramaje_cubierta");
if (gramajesCubierta != null && !gramajesCubierta.isEmpty()) {
String gramajeActual = presupuesto.getGramajeCubierta().toString();
if (!gramajesCubierta.contains(gramajeActual)) {
presupuesto.setGramajeCubierta(Integer.parseInt(gramajesCubierta.get(0))); // Asignar primera opción
}
}
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-acabados-cubierta")
public ResponseEntity<?> getAcabadosCubierta(
Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, Object> resultado = presupuestoService.obtenerOpcionesAcabadosCubierta(presupuesto, locale);
return ResponseEntity.ok(resultado);
}
@PostMapping("/public/get-price")
public ResponseEntity<?> getPrice(
Presupuesto presupuesto,
BindingResult result, Locale locale) {
Map<String, String> errores = new HashMap<>();
// errores de campos individuales
result.getFieldErrors().forEach(error -> errores.put(error.getField(), error.getDefaultMessage()));
// errores globales (@ConsistentTiradas...)
result.getGlobalErrors().forEach(error -> errores.put("global", error.getDefaultMessage()));
if (!errores.isEmpty()) {
return ResponseEntity.badRequest().body(errores);
}
String price = apiClient.getPrice(presupuestoService.toSkApiRequest(presupuesto));
if (price == null || price.isEmpty()) {
return ResponseEntity.badRequest().body("No se pudo obtener el precio. Intente nuevamente.");
}
return ResponseEntity.ok(price);
}
}

View File

@ -0,0 +1,581 @@
package com.imprimelibros.erp.presupuesto;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.Locale;
import java.text.NumberFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.imprimelibros.erp.configurationERP.VariableService;
import com.imprimelibros.erp.presupuesto.Presupuesto.TipoCubierta;
import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto;
import com.imprimelibros.erp.presupuesto.classes.PresupuestadorItems;
import com.imprimelibros.erp.externalApi.skApiClient;
@Service
public class PresupuestoService {
@Autowired
protected VariableService variableService;
@Autowired
protected MessageSource messageSource;
@Autowired
protected skApiClient skApiClient;
private final PresupuestadorItems presupuestadorItems;
public PresupuestoService(PresupuestadorItems presupuestadorItems) {
this.presupuestadorItems = presupuestadorItems;
}
public boolean validateDatosGenerales(int[] tiradas) {
for (int tirada : tiradas) {
if (tirada <= 0) {
return false; // Invalid tirada found
}
}
return true;
}
public Boolean isPOD(Presupuesto presupuesto) {
int pod_value = variableService.getValorEntero("POD");
return (presupuesto.getTirada1() != null && presupuesto.getTirada1() <= pod_value) ||
(presupuesto.getTirada2() != null && presupuesto.getTirada2() <= pod_value) ||
(presupuesto.getTirada3() != null && presupuesto.getTirada3() <= pod_value) ||
(presupuesto.getTirada4() != null && presupuesto.getTirada4() <= pod_value);
}
public Map<String, Object> obtenerOpcionesColor(Presupuesto presupuesto, Locale locale) {
List<ImagenPresupuesto> opciones = new ArrayList<>();
if (presupuesto.getPaginasColor() > 0) {
if (!this.isPOD(presupuesto)) {
// POD solo color foto
ImagenPresupuesto opcionColor = this.presupuestadorItems.getImpresionColor(locale);
opcionColor.setSelected(Presupuesto.TipoImpresion.color.equals(presupuesto.getTipoImpresion()));
opciones.add(opcionColor);
}
ImagenPresupuesto opcionColorHq = this.presupuestadorItems.getImpresionColorPremium(locale);
if (Presupuesto.TipoImpresion.colorhq.equals(presupuesto.getTipoImpresion()))
opcionColorHq.setSelected(true);
opciones.add(opcionColorHq);
} else {
ImagenPresupuesto opcionNegro = this.presupuestadorItems.getImpresionNegro(locale);
if (Presupuesto.TipoImpresion.negro.equals(presupuesto.getTipoImpresion()))
opcionNegro.setSelected(true);
opciones.add(opcionNegro);
ImagenPresupuesto opcionNegroHq = this.presupuestadorItems.getImpresionNegroPremium(locale);
if (Presupuesto.TipoImpresion.negrohq.equals(presupuesto.getTipoImpresion()))
opcionNegroHq.setSelected(true);
opciones.add(opcionNegroHq);
}
boolean opcionSeleccionada = opciones.stream().anyMatch(ImagenPresupuesto::isSelected);
if (!opcionSeleccionada) {
opciones.get(0).setSelected(true);
presupuesto.setTipoImpresion(Presupuesto.TipoImpresion.valueOf(opciones.get(0).getId()));
}
Map<String, Object> response = new HashMap<>();
response.put("opciones_color", opciones);
return response;
}
public Map<String, Object> obtenerOpcionesPapelInterior(Presupuesto presupuesto, Locale locale) {
List<ImagenPresupuesto> opciones = new ArrayList<>();
opciones.add(this.presupuestadorItems.getPapelOffsetBlanco(locale));
if (presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negro ||
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.color) {
opciones.add(this.presupuestadorItems.getPapelOffsetBlancoVolumen(locale));
}
opciones.add(this.presupuestadorItems.getPapelOffsetAhuesado(locale));
if (presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negro ||
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.color) {
opciones.add(this.presupuestadorItems.getPapelOffsetAhuesadoVolumen(locale));
}
opciones.add(this.presupuestadorItems.getPapelEstucadoMate(locale));
for (ImagenPresupuesto imagenPresupuesto : opciones) {
imagenPresupuesto.setSelected(
presupuesto.getPapelInteriorId() != null
&& imagenPresupuesto.getExtra_data().get("sk-id").equals(
String.valueOf(presupuesto.getPapelInteriorId())));
}
boolean yaSeleccionado = opciones.stream().anyMatch(ImagenPresupuesto::isSelected);
if (!yaSeleccionado && !opciones.isEmpty()) {
ImagenPresupuesto primeraOpcion = opciones.get(0);
primeraOpcion.setSelected(true);
presupuesto.setPapelInteriorId(Integer.parseInt(primeraOpcion.getExtra_data().get("sk-id")));
}
Map<String, Object> response = new HashMap<>();
response.put("opciones_papel_interior", opciones);
return response;
}
public Map<String, Object> obtenerOpcionesGramajeInterior(Presupuesto presupuesto) {
List<String> gramajes = new ArrayList<>();
final int BLANCO_OFFSET_ID = 3;
final int BLANCO_OFFSET_VOLUMEN_ID = 7;
final int AHUESADO_OFFSET_ID = 4;
final int AHUESADO_OFFSET_VOLUMEN_ID = 6;
final int ESTUCADO_MATE_ID = 2;
if (presupuesto.getPapelInteriorId() != null && presupuesto.getPapelInteriorId() == BLANCO_OFFSET_ID) {
gramajes.add("80");
gramajes.add("90");
if (presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negrohq ||
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.colorhq) {
gramajes.add("100");
gramajes.add("120");
gramajes.add("150");
gramajes.add("170");
}
} else if (presupuesto.getPapelInteriorId() != null
&& presupuesto.getPapelInteriorId() == BLANCO_OFFSET_VOLUMEN_ID) {
if (presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negro ||
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.color) {
gramajes.add("80");
}
} else if (presupuesto.getPapelInteriorId() != null && presupuesto.getPapelInteriorId() == AHUESADO_OFFSET_ID) {
gramajes.add("80");
gramajes.add("90");
if (presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negrohq ||
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.colorhq) {
gramajes.add("100");
}
} else if (presupuesto.getPapelInteriorId() != null
&& presupuesto.getPapelInteriorId() == AHUESADO_OFFSET_VOLUMEN_ID) {
if (presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negro ||
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.color) {
gramajes.add("70");
gramajes.add("80");
}
} else if (presupuesto.getPapelInteriorId() != null && presupuesto.getPapelInteriorId() == ESTUCADO_MATE_ID) {
if (presupuesto.getTipoImpresion() != Presupuesto.TipoImpresion.color) {
gramajes.add("90");
}
if (presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negrohq ||
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.colorhq) {
gramajes.add("100");
gramajes.add("115");
}
if (presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negro ||
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.color) {
gramajes.add("120");
}
if (presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.negrohq ||
presupuesto.getTipoImpresion() == Presupuesto.TipoImpresion.colorhq) {
gramajes.add("125");
gramajes.add("135");
gramajes.add("150");
gramajes.add("170");
gramajes.add("200");
}
}
Map<String, Object> response = new HashMap<>();
response.put("opciones_gramaje_interior", gramajes);
return response;
}
public Map<String, Object> obtenerOpcionesPapelCubierta(Presupuesto presupuesto, Locale locale) {
List<ImagenPresupuesto> opciones = new ArrayList<>();
if (presupuesto.getTipoCubierta() == Presupuesto.TipoCubierta.tapaBlanda) {
opciones.add(this.presupuestadorItems.getCartulinaGraficaCubierta(locale));
}
opciones.add(this.presupuestadorItems.getEstucadoMateCubierta(locale));
for (ImagenPresupuesto imagenPresupuesto : opciones) {
imagenPresupuesto.setSelected(
presupuesto.getPapelCubiertaId() != null
&& imagenPresupuesto.getExtra_data().get("sk-id").equals(
String.valueOf(presupuesto.getPapelCubiertaId())));
}
Map<String, Object> response = new HashMap<>();
response.put("opciones_papel_cubierta", opciones);
return response;
}
public Map<String, Object> obtenerOpcionesGramajeCubierta(Presupuesto presupuesto) {
List<String> gramajes = new ArrayList<>();
final int CARTULINA_GRAFICA_ID = 5;
final int ESTUCADO_MATE_ID = 2;
if (presupuesto.getPapelCubiertaId() != null && presupuesto.getPapelCubiertaId() == CARTULINA_GRAFICA_ID) {
gramajes.add("240");
gramajes.add("270");
gramajes.add("300");
gramajes.add("350");
} else if (presupuesto.getPapelCubiertaId() != null && presupuesto.getPapelCubiertaId() == ESTUCADO_MATE_ID) {
if (presupuesto.getTipoCubierta() == Presupuesto.TipoCubierta.tapaBlanda) {
gramajes.add("250");
gramajes.add("300");
gramajes.add("350");
} else {
gramajes.add("170");
}
}
Map<String, Object> response = new HashMap<>();
response.put("opciones_gramaje_cubierta", gramajes);
return response;
}
public Map<String, Object> toSkApiRequest(Presupuesto presupuesto) {
final int SK_CLIENTE_ID = 1284;
final int SK_PAGINAS_CUADERNILLO = 32;
Map<String, Object> tamanio = Map.of(
"ancho", presupuesto.getAncho(),
"alto", presupuesto.getAlto());
Map<String, Object> interior = Map.of(
"papelInterior", presupuesto.getPapelInteriorId(),
"gramajeInterior", presupuesto.getGramajeInterior());
Map<String, Object> cubierta = Map.of(
"tipoCubierta", presupuesto.getTipoCubierta().name(),
"papelCubierta", presupuesto.getPapelCubiertaId(),
"gramajeCubierta", presupuesto.getGramajeCubierta(),
"carasImpresion", presupuesto.getCubiertaCaras(),
"solapas", presupuesto.getSolapasCubierta() ? presupuesto.getTamanioSolapasCubierta() : 0,
"acabado", presupuesto.getAcabado(),
"cabezada", presupuesto.getCabezada(),
"lomoRedondo", presupuesto.getTipoCubierta() == TipoCubierta.tapaDuraLomoRedondo ? 1 : 0);
/*
* Map<String, Object> servicios = Map.of(
* "retractilado", 0,
* "retractilado5", 0,
* "ferro", 0,
* "ferroDigital", 0,
* "marcapaginas", 0,
* "prototipo", 0);
*/
Map<String, Object> body = new HashMap<>();
body.put("tipo_impresion_id", this.getTipoImpresionId(presupuesto));
body.put("tirada", Arrays.stream(presupuesto.getTiradas())
.filter(Objects::nonNull)
.collect(Collectors.toList()));
body.put("tamanio", tamanio);
body.put("tipo", presupuesto.getTipoEncuadernacion());
body.put("clienteId", SK_CLIENTE_ID);
body.put("isColor", presupuesto.getTipoImpresion().name().contains("color") ? 1 : 0);
body.put("isHq", presupuesto.getTipoImpresion().name().contains("hq") ? 1 : 0);
body.put("paginas", presupuesto.getPaginasNegro() + presupuesto.getPaginasColor());
body.put("paginasColor", presupuesto.getPaginasColor());
body.put("paginasCuadernillo", SK_PAGINAS_CUADERNILLO);
body.put("interior", interior);
body.put("cubierta", cubierta);
body.put("guardas", null);
if (presupuesto.getSobrecubierta()) {
Map<String, Object> sobrecubierta = new HashMap<>();
sobrecubierta.put("papel", presupuesto.getPapelSobrecubiertaId());
sobrecubierta.put("gramaje", presupuesto.getGramajeSobrecubierta());
sobrecubierta.put("solapas", presupuesto.getTamanioSolapasSobrecubierta());
sobrecubierta.put("acabado", presupuesto.getAcabadoSobrecubierta());
body.put("sobrecubierta", sobrecubierta);
}
if (presupuesto.getFaja()) {
Map<String, Object> faja = new HashMap<>();
faja.put("papel", presupuesto.getPapelFajaId());
faja.put("gramaje", presupuesto.getGramajeFaja());
faja.put("solapas", presupuesto.getTamanioSolapasFaja());
faja.put("acabado", presupuesto.getAcabadoFaja());
faja.put("alto", presupuesto.getAltoFaja());
body.put("faja", faja);
}
// body.put("servicios", servicios);
return body;
}
public Integer getTipoImpresionId(Presupuesto presupuesto) {
if (presupuesto.getTipoEncuadernacion() == Presupuesto.TipoEncuadernacion.fresado) {
if (presupuesto.getTipoCubierta() == Presupuesto.TipoCubierta.tapaDura ||
presupuesto.getTipoCubierta() == Presupuesto.TipoCubierta.tapaDuraLomoRedondo) {
return 1; // Fresado tapa dura
} else {
return 2; // Fresado tapa blanda
}
} else if (presupuesto.getTipoEncuadernacion() == Presupuesto.TipoEncuadernacion.cosido) {
if (presupuesto.getTipoCubierta() == Presupuesto.TipoCubierta.tapaDura ||
presupuesto.getTipoCubierta() == Presupuesto.TipoCubierta.tapaDuraLomoRedondo) {
return 3; // Cosido tapa dura
} else {
return 4; // Cosido tapa blanda
}
} else if (presupuesto.getTipoEncuadernacion() == Presupuesto.TipoEncuadernacion.espiral) {
if (presupuesto.getTipoCubierta() == Presupuesto.TipoCubierta.tapaDura ||
presupuesto.getTipoCubierta() == Presupuesto.TipoCubierta.tapaDuraLomoRedondo) {
return 5; // Espiral tapa dura
} else {
return 6; // Espiral tapa blanda
}
} else if (presupuesto.getTipoEncuadernacion() == Presupuesto.TipoEncuadernacion.wireo) {
if (presupuesto.getTipoCubierta() == Presupuesto.TipoCubierta.tapaDura ||
presupuesto.getTipoCubierta() == Presupuesto.TipoCubierta.tapaDuraLomoRedondo) {
return 7; // Wireo tapa dura
} else {
return 8; // Wireo tapa blanda
}
} else if (presupuesto.getTipoEncuadernacion() == Presupuesto.TipoEncuadernacion.grapado) {
return 21; // Grapado
} else {
return 0; // Default case, no valid type
}
}
public Map<String, Object> obtenerOpcionesAcabadosCubierta(Presupuesto presupuesto, Locale locale) {
Map<String, Object> resultado = new HashMap<>();
List<Object> opciones = new ArrayList<>();
opciones.add(new HashMap<>() {
{
put("name", messageSource.getMessage("presupuesto.acabado-ninguno", null, locale));
put("sk-id", 0);
}
});
opciones.add(new HashMap<>() {
{
put("name", messageSource.getMessage("presupuesto.acabado-plastificado-brillo-1c", null, locale));
put("sk-id", 1);
}
});
opciones.add(new HashMap<>() {
{
put("name", messageSource.getMessage("presupuesto.acabado-plastificado-mate-1c", null, locale));
put("sk-id", 5);
}
});
opciones.add(new HashMap<>() {
{
put("name",
messageSource.getMessage("presupuesto.acabado-plastificado-mate-1c-antirrayado", null, locale));
put("sk-id", 8);
}
});
opciones.add(new HashMap<>() {
{
put("name", messageSource.getMessage("presupuesto.acabado-plastificado-mate-uvi", null, locale));
put("sk-id", 2);
}
});
opciones.add(new HashMap<>() {
{
put("name", messageSource.getMessage("presupuesto.acabado-plastificado-mate-uvi3d", null, locale));
put("sk-id", 3);
}
});
opciones.add(new HashMap<>() {
{
put("name", messageSource.getMessage("presupuesto.acabado-plastificado-mate-uvi-braile", null, locale));
put("sk-id", 4);
}
});
opciones.add(new HashMap<>() {
{
put("name", messageSource.getMessage("presupuesto.acabado-plastificado-sandy-1c", null, locale));
put("sk-id", 9);
}
});
resultado.put("opciones_acabados_cubierta", opciones);
return resultado;
}
public Map<String, Object> aplicarMargenTiradas(Map<String, Object> data) {
// implementar margenes
return (Map<String, Object>) data;
}
public String obtenerPrecioRetractilado(Presupuesto presupuesto, Locale locale) {
Integer[] tiradas = presupuesto.getTiradas();
Integer tirada_min = Arrays.stream(tiradas)
.filter(Objects::nonNull)
.min(Integer::compareTo)
.orElse(0);
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("tirada",
presupuesto.getSelectedTirada() != null ? presupuesto.getSelectedTirada() : tirada_min);
Double precio_retractilado = skApiClient.getRetractilado(requestBody);
return precio_retractilado != null
? NumberFormat.getNumberInstance(locale)
.format(Math.round(precio_retractilado * 100.0) / 100.0)
: "0,00";
}
public Map<String, Object> obtenerServiciosExtras(Presupuesto presupuesto, Locale locale, skApiClient apiClient) {
List<Object> opciones = new ArrayList<>();
Double price_prototipo = this.obtenerPrototipo(presupuesto, apiClient);
opciones.add(new HashMap<String, String>() {
{
put("id", "retractilado");
put("title", messageSource.getMessage("presupuesto.extras-retractilado", null, locale));
put("description", "");
put("price", obtenerPrecioRetractilado(presupuesto, locale));
put("priceUnit", messageSource.getMessage("app.currency-symbol", null, locale));
}
});
opciones.add(new HashMap<String, String>() {
{
put("id", "service-isbn");
put("title", messageSource.getMessage("presupuesto.extras-isbn", null, locale));
put("description", "");
put("price", "30");
put("priceUnit", messageSource.getMessage("app.currency-symbol", null, locale));
}
});
opciones.add(new HashMap<String, String>() {
{
put("id", "deposito-legal");
put("title", messageSource.getMessage("presupuesto.extras-deposito-legal", null, locale));
put("description",
messageSource.getMessage("presupuesto.extras-deposito-legal-descripcion", null, locale));
put("price", "30");
put("priceUnit", messageSource.getMessage("app.currency-symbol", null, locale));
}
});
opciones.add(new HashMap<String, String>() {
{
put("id", "revision-archivos");
put("title", messageSource.getMessage("presupuesto.extras-revision-archivos", null, locale));
put("description", "");
put("price", "50");
put("priceUnit", messageSource.getMessage("app.currency-symbol", null, locale));
}
});
opciones.add(new HashMap<String, String>() {
{
put("id", "maquetacion-cubierta");
put("title", messageSource.getMessage("presupuesto.extras-maquetacion-cubierta", null, locale));
put("description", "");
put("price", "50");
put("priceUnit", messageSource.getMessage("app.currency-symbol", null, locale));
}
});
opciones.add(new HashMap<String, String>() {
{
put("id", "ferro-digital");
put("title", messageSource.getMessage("presupuesto.extras-ferro-digital", null, locale));
put("description", "");
put("price", "0");
put("priceUnit", messageSource.getMessage("app.currency-symbol", null, locale));
put("checked", "true");
put("allowChange", "false");
put("ribbonText", messageSource.getMessage("presupuesto.extras-ferro-digital-ribbon", null, locale));
}
});
opciones.add(new HashMap<String, String>() {
{
put("id", "ejemplar-prueba");
put("title", messageSource.getMessage("presupuesto.extras-ejemplar-prueba", null, locale));
put("description", "");
if(price_prototipo == 0.0) {
put("price", messageSource.getMessage("presupuesto.consultar-soporte", null, locale));
put("priceUnit", "");
} else {
put("price", NumberFormat.getNumberInstance(locale)
.format(Math.round(price_prototipo * 100.0) / 100.0));
put("priceUnit", messageSource.getMessage("app.currency-symbol", null, locale));
}
}
});
opciones.add(new HashMap<String, String>() {
{
put("id", "marcapaginas");
put("title", messageSource.getMessage("presupuesto.extras-marcapaginas", null, locale));
put("description", "");
put("price", messageSource.getMessage("presupuesto.extras-calcular", null, locale));
}
});
opciones.add(new HashMap<String, String>() {
{
put("id", "maquetacion");
put("title", messageSource.getMessage("presupuesto.extras-maquetacion", null, locale));
put("description", "");
put("price", messageSource.getMessage("presupuesto.extras-calcular", null, locale));
}
});
Map<String, Object> response = new HashMap<>();
response.put("servicios_extra", opciones);
return response;
}
private Double obtenerPrototipo(Presupuesto presupuesto, skApiClient apiClient) {
// Obtenemos el precio de 1 unidad para el ejemplar de prueba
HashMap<String, Object> price = new HashMap<>();
// make a copy of "presupuesto" to avoid modifying the original object
Presupuesto presupuestoTemp = presupuesto.clone();
presupuestoTemp.setTirada1(1);
presupuestoTemp.setTirada2(null);
presupuestoTemp.setTirada3(null);
presupuestoTemp.setTirada4(null);
if (presupuestoTemp.getTipoImpresion() == Presupuesto.TipoImpresion.color) {
presupuestoTemp.setTipoImpresion(Presupuesto.TipoImpresion.colorhq);
} else if (presupuestoTemp.getTipoImpresion() == Presupuesto.TipoImpresion.negro) {
presupuestoTemp.setTipoImpresion(Presupuesto.TipoImpresion.negrohq);
}
String priceStr = apiClient.getPrice(this.toSkApiRequest(presupuestoTemp));
Double price_prototipo = 0.0;
try {
price = new ObjectMapper().readValue(priceStr, new TypeReference<>() {
});
price_prototipo = ((List<Double>) ((Map<String, Object>) price.get("data")).get("precios")).get(0);
if (price_prototipo < 25) {
price_prototipo = 25.0;
}
} catch (JsonProcessingException e) {
} catch (Exception exception) {
}
return price_prototipo;
}
}

View File

@ -0,0 +1,85 @@
package com.imprimelibros.erp.presupuesto.classes;
import java.util.Map;
public class ImagenPresupuesto {
private String id;
private String imagen;
private String alt;
private String texto;
private boolean selected = false;
private Map<String, String> extra_data;
// Constructores
public ImagenPresupuesto() {}
public ImagenPresupuesto(String id, String imagen, String alt, String texto, boolean selected) {
this.id = id;
this.imagen = imagen;
this.alt = alt;
this.texto = texto;
this.selected = selected;
}
public ImagenPresupuesto(String id, String imagen, String alt, String texto, Map<String, String> extra_data,boolean selected) {
this.id = id;
this.imagen = imagen;
this.alt = alt;
this.texto = texto;
this.selected = selected;
this.extra_data = extra_data;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getImagen() {
return imagen;
}
public void setImagen(String imagen) {
this.imagen = imagen;
}
public String getAlt() {
return alt;
}
public void setAlt(String alt) {
this.alt = alt;
}
public String getTexto() {
return texto;
}
public void setTexto(String texto) {
this.texto = texto;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
public Map<String, String> getExtra_data() {
return extra_data;
}
public void setExtra_data(Map<String, String> extra_data) {
this.extra_data = extra_data;
}
}

View File

@ -0,0 +1,129 @@
package com.imprimelibros.erp.presupuesto.classes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Component;
import java.util.Locale;
import java.util.Map;
@Component
public class PresupuestadorItems {
@Autowired
private MessageSource messageSource;
public ImagenPresupuesto getImpresionNegro(Locale locale) {
return new ImagenPresupuesto(
"negro",
"/assets/images/imprimelibros/presupuestador/blancoYnegro.png",
"",
messageSource.getMessage("presupuesto.blanco-negro", null, locale),
false);
}
public ImagenPresupuesto getImpresionNegroPremium(Locale locale) {
return new ImagenPresupuesto(
"negrohq",
"/assets/images/imprimelibros/presupuestador/negroFoto.png",
"",
messageSource.getMessage("presupuesto.blanco-negro-premium", null, locale),
false);
}
public ImagenPresupuesto getImpresionColor(Locale locale) {
return new ImagenPresupuesto(
"color",
"/assets/images/imprimelibros/presupuestador/color.png",
"",
messageSource.getMessage("presupuesto.color", null, locale),
false);
}
public ImagenPresupuesto getImpresionColorPremium(Locale locale) {
return new ImagenPresupuesto(
"colorhq",
"/assets/images/imprimelibros/presupuestador/colorFoto.png",
"",
messageSource.getMessage("presupuesto.color-premium", null, locale),
false);
}
public ImagenPresupuesto getPapelOffsetBlanco(Locale locale) {
return new ImagenPresupuesto(
"offset-blanco",
"/assets/images/imprimelibros/presupuestador/offset-blanco.png",
"",
messageSource.getMessage("presupuesto.offset-blanco", null, locale),
Map.of("sk-id", "3"),
false);
}
public ImagenPresupuesto getPapelOffsetBlancoVolumen(Locale locale) {
return new ImagenPresupuesto(
"offset-blanco-volumen",
"/assets/images/imprimelibros/presupuestador/offset-blanco.png",
"",
messageSource.getMessage("presupuesto.offset-blanco-volumen", null, locale),
Map.of("sk-id", "7"),
false);
}
public ImagenPresupuesto getPapelOffsetAhuesado(Locale locale) {
return new ImagenPresupuesto(
"offset-ahuesado",
"/assets/images/imprimelibros/presupuestador/offset-ahuesado.png",
"",
messageSource.getMessage("presupuesto.offset-ahuesado", null, locale),
Map.of("sk-id", "4"),
false);
}
public ImagenPresupuesto getPapelOffsetAhuesadoVolumen(Locale locale) {
return new ImagenPresupuesto(
"offset-ahuesado-volumen",
"/assets/images/imprimelibros/presupuestador/offset-ahuesado-volumen.png",
"",
messageSource.getMessage("presupuesto.offset-ahuesado-volumen", null, locale),
Map.of("sk-id", "6"),
false);
}
public ImagenPresupuesto getPapelEstucadoMate(Locale locale) {
return new ImagenPresupuesto(
"estucado-mate",
"/assets/images/imprimelibros/presupuestador/estucado-mate.png",
"",
messageSource.getMessage("presupuesto.estucado-mate", null, locale),
Map.of("sk-id", "2"),
false);
}
public ImagenPresupuesto getCartulinaGraficaCubierta(Locale locale) {
return new ImagenPresupuesto(
"cartulina-grafica-cubierta",
"/assets/images/imprimelibros/presupuestador/cartulina-grafica.png",
"",
messageSource.getMessage("presupuesto.cartulina-grafica-cubierta", null, locale),
Map.of("sk-id", "5"),
false);
}
public ImagenPresupuesto getEstucadoMateCubierta(Locale locale) {
return new ImagenPresupuesto(
"estucado-mate-cubierta",
"/assets/images/imprimelibros/presupuestador/estucado-mate-cubierta.png",
"",
messageSource.getMessage("presupuesto.estucado-mate-cubierta", null, locale),
Map.of("sk-id", "2"),
false);
}
}

View File

@ -0,0 +1,16 @@
package com.imprimelibros.erp.presupuesto.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = ConsistentTiradasValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ConsistentTiradas {
String message() default "Las tiradas deben ser todas mayores o todas menores al valor POD";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,53 @@
package com.imprimelibros.erp.presupuesto.validation;
import com.imprimelibros.erp.configurationERP.VariableService;
import com.imprimelibros.erp.presupuesto.Presupuesto;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
public class ConsistentTiradasValidator implements ConstraintValidator<ConsistentTiradas, Presupuesto> {
@Autowired
private VariableService variableService;
@Autowired
private MessageSource messageSource;
@Override
public boolean isValid(Presupuesto presupuesto, ConstraintValidatorContext context) {
if (presupuesto == null)
return true;
Integer[] tiradas = presupuesto.getTiradas();
Integer podValue = variableService.getValorEntero("POD");
boolean allAbove = true;
boolean allBelow = true;
for (Integer t : tiradas) {
if (t == null)
continue;
if (t <= podValue)
allAbove = false;
else // (t > podValue)
allBelow = false;
}
if (!(allAbove || allBelow)) {
String mensajeInterpolado = messageSource.getMessage(
"presupuesto.errores.tiradas.consistentes", // clave del mensaje
new Object[] { podValue }, // parámetros para {0}
LocaleContextHolder.getLocale() // respeta el idioma actual
);
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(mensajeInterpolado)
.addConstraintViolation();
return false;
}
return true;
}
}

View File

@ -0,0 +1,16 @@
package com.imprimelibros.erp.presupuesto.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = ParValidator.class)
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface Par {
String message() default "El valor debe ser un número par";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,14 @@
package com.imprimelibros.erp.presupuesto.validation;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class ParValidator implements ConstraintValidator<Par, Integer> {
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (value == null) return true; // se permite null, usa @NotNull aparte si lo necesitas
return value % 2 == 0;
}
}

View File

@ -0,0 +1,9 @@
package com.imprimelibros.erp.presupuesto.validation;
public class PresupuestoValidationGroups {
public interface DatosGenerales {}
public interface Interior {}
public interface Cubierta {}
}

View File

@ -0,0 +1,19 @@
package com.imprimelibros.erp.presupuesto.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = TamanioValidator.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Tamanio {
String message() default "{presupuesto.errores.tamanio.invalido}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,55 @@
package com.imprimelibros.erp.presupuesto.validation;
import com.imprimelibros.erp.configurationERP.VariableService;
import com.imprimelibros.erp.presupuesto.Presupuesto;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
public class TamanioValidator implements ConstraintValidator<Tamanio, Presupuesto>{
@Autowired
private VariableService variableService;
@Autowired
private MessageSource messageSource;
@Override
public boolean isValid(Presupuesto presupuesto, ConstraintValidatorContext context) {
if (presupuesto == null)
return true;
Integer min = variableService.getValorEntero("ancho_alto_min");
Integer max = variableService.getValorEntero("ancho_alto_max");
if (presupuesto.getAncho() <= min || presupuesto.getAncho() >= max) {
String mensajeInterpolado = messageSource.getMessage(
"presupuesto.errores.ancho.min_max", // clave del mensaje
new Object[] { min, max }, // parámetros para {0}
LocaleContextHolder.getLocale() // respeta el idioma actual
);
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(mensajeInterpolado)
.addConstraintViolation();
return false;
}
else if (presupuesto.getAlto() <= min || presupuesto.getAlto() >= max) {
String mensajeInterpolado = messageSource.getMessage(
"presupuesto.errores.alto.min_max", // clave del mensaje
new Object[] { min, max }, // parámetros para {0}
LocaleContextHolder.getLocale() // respeta el idioma actual
);
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(mensajeInterpolado)
.addConstraintViolation();
return false;
}
return true;
}
}

View File

@ -1 +1,18 @@
spring.application.name=erp
logging.level.org.springframework.security=DEBUG
logging.level.root=WARN
logging.level.org.springframework=ERROR
spring.datasource.url=jdbc:mysql://127.0.0.1:3309/imprimelibros?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Europe/Madrid&characterEncoding=utf8
spring.datasource.username=imprimelibros_user
spring.datasource.password=om91irrDctd
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#safekat.api.url=http://localhost:8000/
safekat.api.url=https://erp-dev.safekat.es/
safekat.api.email=imnavajas@coit.es
safekat.api.password=Safekat2024
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

View File

@ -0,0 +1 @@
app.currency-symbol=

View File

@ -0,0 +1,3 @@
app.currency-symbol=
app.yes=
app.no=No

View File

@ -1 +0,0 @@
t-paginas=Pages

View File

@ -1,2 +0,0 @@
t-home=Home
t-home-title=Welcome to the Print Go Application

View File

@ -1,3 +0,0 @@
t-menu=Menu
t-menu-config=Configuration
t-menu-config-impresioras=Printers

View File

@ -1,4 +0,0 @@
t-printers=Printers
t-printers-list=Printers List
t-printers-id=ID
t-printers-name=Name

View File

@ -1 +0,0 @@
t-paginas=Páginas

View File

@ -1,2 +0,0 @@
t-home=Inicio
t-home-title=Bienvenido a la aplicación Print Go

View File

@ -1,3 +0,0 @@
t-menu=Menú
t-menu-config=Configuración
t-menu-config-impresioras=Impresoras

View File

@ -1,4 +0,0 @@
t-printers=Impresoras
t-printers-list=Lista de Impresoras
t-printers-id=ID
t-printers-name=Nombre

View File

@ -0,0 +1 @@
login.login=Login

View File

@ -0,0 +1 @@
login.login=Iniciar sesión

View File

@ -0,0 +1,4 @@
presupuesto.datos-generales=General Data
presupuesto.interior=Inside
presupuesto.cubierta=Cover
presupuesto.envio=Shipping

View File

@ -0,0 +1,184 @@
presupuesto.datos-generales=Datos Generales
presupuesto.interior=Interior
presupuesto.cubierta=Cubierta
presupuesto.seleccion-tirada=Seleccion de tirada
presupuesto.extras=Extras
# Pestaña datos generales de presupuesto
presupuesto.informacion-libro=Información del libro
presupuesto.datos-generales-descripcion=Datos generales del presupuesto
presupuesto.titulo=Título*
presupuesto.autor=Autor
presupuesto.isbn=ISBN
presupuesto.tirada1=Tirada 1*
presupuesto.tirada2=Tirada 2
presupuesto.tirada3=Tirada 3
presupuesto.tirada4=Tirada 4
presupuesto.tiradasPODnoPOD=No puede mezclar tiradas menores de {0} unidades con mayores de {0} unidades
presupuesto.formato=Formato
presupuesto.orientacion.vertical=Vertical
presupuesto.orientacion.apaisado=Apaisado
presupuesto.orientacion.cuadrado=Cuadrado
presupuesto.formato-personalizado=Formato personalizado
presupuesto.ancho=Ancho (mm)*
presupuesto.alto=Alto (mm)*
presupuesto.paginas-total=Total Páginas
presupuesto.paginas-negro=Páginas Negro
presupuesto.paginas-color=Páginas Color
presupuesto.paginas-posicion=Posición páginas color
presupuesto.paginas-posicion-descripcion=Intruzca la posición separada por comas. Ej: 3,5,7 ó 4-10,20,155
presupuesto.siempre-pares=Siempre deben ser pares
presupuesto.encuadernacion=Encuadernación
presupuesto.fresado=Fresado
presupuesto.fresado-descripcion=Fresado (a partir de 32 páginas)
presupuesto.cosido=Cosido
presupuesto.cosido-descripcion=Cosido (a partir de 32 páginas)
presupuesto.grapado=Grapado
presupuesto.grapado-descripcion=Grapado (entre 12 y 40 páginas)
presupuesto.espiral=Espiral
presupuesto.espiral-descripcion=Espiral (a partir de 20 páginas)
presupuesto.wire-o=Wire-O
presupuesto.wire-o-descripcion=Wire-O (a partir de 20 páginas)
presupuesto.encuadernacion-descripcion=Seleccione la encuadernación del libro
presupuesto.continuar-interior=Continuar a diseño interior
# Pestaña interior de presupuesto
presupuesto.tipo-interior=Tipo de impresión
presupuesto.tipo-interior-descripcion=Seleccione entre calidad estándar o premium
presupuesto.papel-interior=Papel interior
presupuesto.papel-interior-descripcion=Seleccione el papel para el interior
presupuesto.gramaje-interior=Gramaje interior
presupuesto.gramaje-interior-descripcion=Seleccione el gramaje para el interior
presupuesto.blanco-negro=Blanco y negro
presupuesto.blanco-negro-premium= Blanco y negro Premium
presupuesto.color=Color
presupuesto.color-premium=Color Premium
presupuesto.offset-blanco=Offset Blanco
presupuesto.offset-blanco-volumen=Offset Blanco Volumen
presupuesto.offset-ahuesado=Offset Ahuesado
presupuesto.offset-ahuesado-volumen=Offset Ahuesado Volumen
presupuesto.estucado-mate=Estucado Mate
presupuesto.volver-datos-generales=Volver a datos generales
presupuesto.continuar-cubierta=Continuar a diseño cubierta
# Pestaña cubierta
presupuesto.plantilla-cubierta=Plantilla de cubierta
presupuesto.plantilla-cubierta-text=Recuerde que la cubierta es el conjunto formado por la portada, contraportada, lomo y solapas, en caso de que las lleve.<br/><br/><br/>Si tiene dudas de las medidas puede solicitarnos una plantilla cuando haga el pedido.
presupuesto.tipo-cubierta=Tipo de cubierta
presupuesto.tipo-cubierta-descripcion=Seleccione el tipo de cubierta y sus opciones
presupuesto.tapa-blanda=Tapa blanda
presupuesto.tapa-dura=Tapa dura
presupuesto.tapa-dura-lomo-redondo=Tapa dura lomo redondo
presupuesto.sin-solapas=Sin solapas
presupuesto.con-solapas=Con solapas
presupuesto.impresion-cubierta=Impresión de cubierta
presupuesto.impresion-cubierta-help=La cubierta se puede imprimir por anverso y reverso, como en el caso de las revistas, pero para un libro normal con portada y contraportada, la impresión de cubierta es a una cara.
presupuesto.una-cara=Una cara
presupuesto.dos-caras=Dos caras
presupuesto.tamanio-solapa=Tamaño solapas
presupuesto.papel-guardas=Papel de guardas
presupuesto.guardas-impresas=Guardas impresas
presupuesto.no=No
presupuesto.cabezada=Cabezada
presupuesto.cabezada-blanca=Blanca
presupuesto.cabezada-verde=Verde
presupuesto.cabezada-azul=Azul
presupuesto.cabezada-roja-amarilla=Roja-Amarilla
presupuesto.papel-cubierta=Papel cubierta
presupuesto.papel-cubierta-descripcion=Seleccione el papel para la cubierta
presupuesto.cartulina-grafica-cubierta=Cartulina gráfica estucada a una cara
presupuesto.estucado-mate-cubierta=Estucado mate
presupuesto.gramaje-cubierta=Gramaje cubierta
presupuesto.gramaje-cubierta-descripcion=Seleccione el gramaje para la cubierta
presupuesto.volver-interior=Volver a diseño interior
presupuesto.continuar-seleccion-tirada=Continuar a selección de tirada
presupuesto.offset=Offset
presupuesto.estucado=Estucado
presupuesto.verjurado=Verjurado
presupuesto.verjurado-blanco-natural=Verjurado blanco natural
presupuesto.verjurado-ahuesado=Verjurado ahuesado
presupuesto.acabado=Acabado
presupuesto.acabado-cubierta-descripcion=Seleccione el acabado para la cubierta
presupuesto.acabado-cubierta-aviso=La falta de plastificado en la cubierta puede comprometer su calidad, ya que aumenta el riesgo de agrietamiento en los pliegues o hendidos, afectando su apariencia y resistencia.
presupuesto.acabado-ninguno=Sin acabado
presupuesto.acabado-plastificado-brillo-1c=Plastificado Brillo 1/C
presupuesto.acabado-plastificado-mate-1c=Plastificado Mate 1/C
presupuesto.acabado-plastificado-mate-1c-antirrayado=Plastificado Mate 1/C Antirrayado
presupuesto.acabado-plastificado-mate-uvi=Plastificado Mate 1/C + Reserva UVI
presupuesto.acabado-plastificado-mate-uvi3d=Plastificado Mate 1/C + Reserva UVI 3D
presupuesto.acabado-plastificado-mate-uvi-braile=Plastificado Mate 1/C + Reserva UVI Braille
presupuesto.acabado-plastificado-sandy-1c=Plastificado Sandy 1/C (tacto arena)
presupuesto.cubierta-extras=Extras de cubierta
presupuesto.cubierta-extras-descripcion=Seleccione las opciones adicionales para la cubierta
presupuesto.sobrecubierta=Sobrecubierta
presupuesto.sobrecubierta-papel=Papel sobrecubierta
presupuesto.sobrecubierta-solapas=Tamaño solapas sobrecubierta
presupuesto.faja=Faja
presupuesto.faja-papel=Papel faja
presupuesto.faja-solapas=Tamaño solapas faja
presupuesto.faja-alto=Alto faja
#pestaña seleccion-tirada
presupuesto.seleccion-tirada-descripcion=Seleccione la tirada deseada
presupuesto.total=Total
presupuesto.precio-unidad=Precio por unidad
presupuesto.seleccionar-tirada=Seleccionar tirada
presupuesto.tirada-seleccionada=Seleccionada
presupuesto.unidades=UNIDADES
presupuesto.volver-seleccion-tirada=Volver a selección de tirada
presupuesto.continuar-extras-libro=Continuar a extras del libro
presupuesto.error-obtener-precio=No se pudo obtener el precio para los datos introducidos. Por favor, contacte con el soporte técnico.
#pestaña extras del libro
presupuesto.extras=Servicios Extras
presupuesto.extras-descripcion=Seleccione los servicios adicionales que desea añadir al presupuesto
presupuesto.extras-retractilado=Retractilado
presupuesto.extras-isbn=ISBN
presupuesto.extras-deposito-legal=Depósito Legal
presupuesto.extras-deposito-legal-descripcion=Se añadirán 4 ejemplares a la tirada
presupuesto.extras-revision-archivos=Revisión de archivos
presupuesto.extras-maquetacion-cubierta=Maquetación de cubierta
presupuesto.extras-ferro-digital=Ferro Digital
presupuesto.extras-ejemplar-prueba=Ejemplar de prueba
presupuesto.extras-marcapaginas=Marcapáginas
presupuesto.extras-maquetacion=Maquetación
presupuesto.extras-ferro-digital-ribbon=Incluido
presupuesto.extras-calcular=Calcular
presupuesto.volver-cubierta=Volver a diseño cubierta
presupuesto.finalizar=Finalizar presupuesto
presupuesto.calcular-presupuesto=Calcular presupuesto
presupuesto.consultar-soporte=Consultar con soporte
# Resumen del presupuesto
presupuesto.resumen-presupuesto=Resumen presupuesto
presupuesto.resumen-libro=Libro
presupuesto.resumen-encuadernacion=Encuadernación
presupuesto.interior-libro=Interior del libro
presupuesto.cubierta-libro=Cubierta del libro
presupuesto.extras-libro=Extras del libro
presupuesto.paginas=Páginas
presupuesto.solapas=Solapas
presupuesto.papel-gramaje=Papel y gramaje
# Errores
presupuesto.errores-title=Corrija los siguientes errores:
presupuesto.errores.titulo=El título es obligatorio
presupuesto.errores.tirada1=La tirada 1 es obligatoria
presupuesto.errores.tiradas.consistentes=Las tiradas deben ser consistentes (no mezclar tiradas menores de {0} unidades con mayores de {0} unidades)
presupuesto.errores.paginasNegro.required=El número de páginas en negro es obligatorio
presupuesto.errores.paginasNegro.par=El número de páginas en negro debe ser par
presupuesto.errores.paginasColor.required=El número de páginas en color es obligatorio
presupuesto.errores.paginasColor.par=El número de páginas en color debe ser par
presupuesto.errores.tipo-encuadernacion=Seleccione el tipo de libro
presupuesto.errores.ancho=El ancho no puede estar vacío
presupuesto.errores.ancho.min_max=El ancho tiene que estar en el rango [{0}, {1}] mm;
presupuesto.errores.alto=El alto no puede estar vacío
presupuesto.errores.alto.min_max=El alto tiene que estar en el rango [{0}, {1}] mm
presupuesto.errores.tipo-impresion=Seleccione el tipo de impresion
presupuesto.errores.papel-interior=Seleccione el tipo de papel para el interior
presupuesto.errores.gramaje-interior=Seleccione el gramaje del papel para el interior
presupuesto.errores.tipo-cubierta=Seleccione el tipo de cubierta
presupuesto.errores.solapas-cubierta=Seleccione si desea o no solapas en la cubierta
presupuesto.errores.papel-cubierta=Seleccione el tipo de papel para la cubierta
presupuesto.errores.gramaje-cubierta=Seleccione el gramaje del papel para la cubierta
presupuesto.errores.acabado-cubierta=Seleccione el acabado de la cubierta

View File

@ -7,8 +7,98 @@ Website: https://themesbrand.com/
Contact: support@themesbrand.com
File: Main Css File
*/
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Public+Sans:wght@300;400;500;600;700&display=swap");
/* poppins-300 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Poppins';
font-style: normal;
font-weight: 300;
src: url('../fonts/poppins/poppins-v23-latin-300.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* poppins-regular - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
src: url('../fonts/poppins/poppins-v23-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* poppins-500 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
src: url('../fonts/poppins/poppins-v23-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* poppins-600 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Poppins';
font-style: normal;
font-weight: 600;
src: url('../fonts/poppins/poppins-v23-latin-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* poppins-700 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Poppins';
font-style: normal;
font-weight: 700;
src: url('../fonts/poppins/poppins-v23-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* public-sans-300 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Public Sans';
font-style: normal;
font-weight: 300;
src: url('../fonts/public-sans/public-sans-v20-latin-300.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* public-sans-regular - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Public Sans';
font-style: normal;
font-weight: 400;
src: url('../fonts/public-sans/public-sans-v20-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* public-sans-500 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Public Sans';
font-style: normal;
font-weight: 500;
src: url('../fonts/public-sans/public-sans-v20-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* public-sans-600 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Public Sans';
font-style: normal;
font-weight: 600;
src: url('../fonts/public-sans/public-sans-v20-latin-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* public-sans-700 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Public Sans';
font-style: normal;
font-weight: 700;
src: url('../fonts/public-sans/public-sans-v20-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
@font-face {
font-family: "hkgrotesk";
src: url("../fonts/hkgrotesk-light.eot");

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
body {
background-color: #fffbdd !important;
}

View File

@ -0,0 +1,350 @@
/* === Contenedor de cada opción === */
.image-container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
/* Tamaño adaptable */
width: 100%;
max-width: 200px;
/* Para evitar que la imagen sobresalga al hacer zoom */
overflow: hidden;
/* Borde invisible por defecto para evitar movimiento al seleccionar */
border: 4px solid transparent;
border-radius: 8px;
padding: 8px;
margin-inline: 5px;
transition: border 0.3s ease;
}
/* === Borde visible cuando está seleccionada === */
.image-container.selected {
border-color: #687cfe;
}
/* === Imagen interna === */
.image-container img {
max-width: 100%;
max-height: 150px;
display: block;
transform-origin: center center;
transition: transform 0.3s ease;
}
/* === Animación de zoom con rebote === */
.image-presupuesto.zoom-anim {
animation: zoomPop 800ms cubic-bezier(0.68, -0.55, 0.27, 1.55);
}
/* Keyframes para la animación */
@keyframes zoomPop {
0% {
transform: scale(1);
}
40% {
transform: scale(0.85);
}
80% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.image-container:hover {
cursor: pointer;
box-shadow: 0 0 10px rgba(104, 124, 254, 0.3);
}
@media (max-width: 576px) {
.image-container {
max-width: 100%;
}
}
.gramaje-radio {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 70px;
/* Ancho mínimo */
min-height: 70px;
/* Alto mínimo */
max-width: 70px;
/* Ancho máximo */
max-height: 70px;
/* Alto máximo */
}
@keyframes fadeInUpBounce {
0% {
opacity: 0;
transform: translateY(30px);
}
60% {
opacity: 1;
transform: translateY(-10px);
}
80% {
transform: translateY(5px);
}
100% {
transform: translateY(0);
}
}
.animate-fadeInUpBounce {
animation: fadeInUpBounce 0.6s ease-out both;
animation-delay: 0.1s;
will-change: transform;
}
.service-option {
cursor: pointer;
transition: all 0.3s ease-in-out;
border: 1px solid #d0d7e5;
border-radius: 0.5rem;
background-color: #fff;
}
.service-option.checked {
border-color: #4b7bec;
background-color: #f0f5ff;
box-shadow: 0 0 0 2px #4b7bec inset;
}
.service-option .price {
font-weight: 600;
font-size: 1rem;
margin-top: 1rem;
}
.service-desc {
min-height: 2.2rem;
/* ajusta según el tamaño de fuente que uses */
display: flex;
align-items: center;
justify-content: center;
}
.service-checkbox {
display: none;
}
.btn-check-service+.btn {
position: relative;
/* <-- Esto es lo más importante */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
white-space: normal;
line-height: 1.2;
min-height: 180px;
padding: 1rem 0.75rem;
border-radius: 0.5rem;
color: #4b7bec;
border: 1px solid #4b7bec;
transition: all 0.3s ease-in-out;
}
/* ESTILO CUANDO ESTÁ MARCADO */
.btn-check-service:checked+.btn {
background-color: #4b7bec;
color: #ffffff !important;
border-color: #4b7bec;
}
/* Forzamos el color blanco en los subelementos */
.btn-check-service:checked+.btn .service-title,
.btn-check-service:checked+.btn .service-desc,
.btn-check-service:checked+.btn .service-price {
color: #ffffff !important;
}
/* Forzamos el color azul cuando no está marcado */
.btn-check-service+.btn .service-title,
.btn-check-service+.btn .service-desc,
.btn-check-service+.btn .service-price {
color: #4b7bec;
}
/* ribbon-service */
.ribbon-service {
position: absolute;
top: -5px;
right: -5px;
overflow: hidden;
width: 90px;
height: 90px;
z-index: 1;
}
.ribbon-service span {
position: absolute;
display: block;
width: 120px;
padding: 5px 0;
background: #f25c5c;
color: white;
font-size: 12px;
text-align: center;
font-weight: bold;
transform: rotate(45deg);
top: 20px;
right: -30px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}
/* ===== Tiradas (pricing cards) ===== */
.tirada-card {
--il-accent: #4b7bec;
--radius: 18px;
--sel-scale-y: 1.12;
/* cuánto más alta la seleccionada (crece arriba y abajo) */
/* círculo */
--arc-w: 280px;
--arc-h: 190px;
--arc-y: -23px;
/* tu valor */
--arc-stop: 74%;
border: 1px solid #e9ecef;
border-radius: var(--radius);
background-color: #fff;
background-image: radial-gradient(var(--arc-w) var(--arc-h) at 50% var(--arc-y),
rgba(75, 126, 236, .24) 0 var(--arc-stop),
transparent calc(var(--arc-stop) + 1%));
padding: 1.25rem 1.25rem 1rem;
text-align: center;
position: relative;
height: 100%;
box-shadow: 0 4px 14px rgba(17, 24, 39, .06);
overflow: hidden;
/* integra la línea inferior */
transform-origin: center center;
/* ⟵ crecer hacia arriba y hacia abajo */
will-change: transform;
transition: transform .22s ease, box-shadow .22s ease, border-color .22s ease, background .22s ease;
}
/* sin elevación al hover */
.tirada-card:hover {
transform: none;
}
/* Tipografías */
.tirada-card .title {
font-weight: 700;
letter-spacing: .04em;
font-size: .9rem;
color: #4b7bec;
text-transform: uppercase;
}
.tirada-card .price-big {
font-size: 2rem;
font-weight: 800;
line-height: 1.1;
margin: .35rem 0 .15rem;
color: #4b7bec;
}
.tirada-card .per {
font-size: .85rem;
color: #7f8fa9;
}
.tirada-card .price-row {
margin-top: .75rem;
}
.tirada-card .muted {
color: #8e9bb3;
font-size: .9rem;
}
/* Botón */
.tirada-card .btn-select-tirada {
margin-top: 1rem;
border-radius: 999px;
padding: .6rem 1rem;
font-weight: 600;
border: 2px solid var(--il-accent);
color: var(--il-accent);
background: transparent;
width: 100%;
transition: background .2s ease, color .2s ease;
}
/* ===== Seleccionada (más alta en Y, sin elevar; crece arriba y abajo) ===== */
.tirada-card.selected {
transform: scaleY(var(--sel-scale-y));
/* ⟵ crecimiento simétrico vertical */
z-index: 2;
box-shadow: 0 18px 48px rgba(75, 126, 236, .32);
border-color: var(--il-accent);
/* círculo sólido */
background-image: radial-gradient(var(--arc-w) var(--arc-h) at 50% var(--arc-y),
#4b7bec 0 var(--arc-stop),
transparent calc(var(--arc-stop) + 1%));
}
.tirada-card.selected:hover {
/* evita que :hover base anule la escala */
transform: scaleY(var(--sel-scale-y));
}
.tirada-card.selected .btn-select-tirada {
background: var(--il-accent);
color: #fff;
}
/* texto en blanco dentro del círculo */
.tirada-card.selected .title,
.tirada-card.selected .price-big,
.tirada-card.selected .per {
color: #fff;
}
/* ===== Línea inferior integrada (siempre visible en NO seleccionadas) ===== */
.tirada-card:not(.selected)::after {
content: "";
position: absolute;
left: 1px;
right: 1px;
bottom: 1px;
/* dentro del borde */
height: 8px;
background: var(--il-accent);
border-bottom-left-radius: calc(var(--radius) - 1px);
border-bottom-right-radius: calc(var(--radius) - 1px);
pointer-events: none;
}
.tirada-card.selected::after {
content: none;
}
/* Oculta el radio */
.tirada-card input[type="radio"] {
display: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
window.languageBundle.get = function (key, ...params) {
let text = this[key] || key;
params.forEach((val, i) => {
text = text.replace(`{${i}}`, val);
});
return text;
};

View File

@ -0,0 +1,66 @@
class imagen_presupuesto {
constructor(data) {
this.id = data.id;
this.imagen = data.imagen;
this.alt = data.alt || "";
this.group = data.group || ""; // Grupo al que pertenece el radio
this.texto = data.texto || ""; // Texto de la etiqueta
this.selected = data.selected || false;
this.extraClass = (data.extraClass && !(data.extraClass===undefined))? (data.extraClass + ' ') : '';
this.extraData = data.extra_data || {}; // Datos extra opcionales
}
render() {
const contenedor = $('<div>', {
id: this.id,
class: `${this.extraClass + ' '}image-container imagen-selector${this.selected ? ' selected' : ''}`
});
// Añadir atributos extra al contenedor
for (const [key, value] of Object.entries(this.extraData)) {
contenedor.attr(`data-${key}`, value);
}
contenedor.attr("data-summary-text", this.texto); // Para el resumen
const input = $('<input>', {
type: 'radio',
name: this.group,
value: this.id,
hidden: true
});
const imagen = $('<img>', {
class: 'image-presupuesto',
id: this.id + '-img',
src: this.imagen,
alt: this.alt
});
const etiqueta = $('<label>', {
for: this.id + '-img',
class: 'form-label',
text: this.texto
});
contenedor.append(imagen, etiqueta);
return contenedor;
}
setSelected(selected) {
this.selected = selected;
const contenedor = $(`#${this.id}`);
const radio = contenedor.find('input[type="radio"]');
if (selected) {
contenedor.addClass('selected');
radio.prop('checked', true).trigger('change'); // <-- esto actualiza el input y lanza evento
} else {
contenedor.removeClass('selected');
radio.prop('checked', false);
}
}
}
export default imagen_presupuesto;

View File

@ -0,0 +1,20 @@
$('.imagen-container-group').on('click', '.image-container', function () {
const clicked = $(this);
const group = clicked.closest('.imagen-container-group');
// Limpiar selección anterior
group.find('.image-container').removeClass('selected')
.find('.image-presupuesto').removeClass('zoom-anim');
// Marcar nuevo seleccionado
clicked.addClass('selected');
// Aplicar animación de zoom
const img = clicked.find('.image-presupuesto');
void img[0].offsetWidth; // Forzar reflow
img.addClass('zoom-anim');
clicked.find('input[type="radio"]').prop('checked', true).trigger('change');
});

View File

@ -0,0 +1,46 @@
class ServiceOptionCard {
constructor({ id, title, description = '', price = '', priceUnit = '', checked = false, allowChange = true, ribbonText }) {
this.id = id;
this.title = title;
this.description = description ? description : '&nbsp;';
this.price = price;
this.priceUnit = priceUnit;
this.checked = checked;
this.allowChange = !(String(allowChange).toLowerCase() === "false");
this.ribbonText = ribbonText;
}
render() {
const ribbonHtml = this.ribbonText
? `<div class="ribbon-service"><span>${this.ribbonText}</span></div>`
: '';
const $card = $(`
<div class="col-lg-2 col-md-3 col-sm-6 mb-3">
<input type="checkbox" class="service-checkbox data-price=${this.price} btn-check-service" id="${this.id}" name="services[]" value="${this.id}" autocomplete="off" ${this.checked ? 'checked' : ''} />
<label class="btn btn-outline-primary w-100 text-center py-3 px-2 d-flex flex-column align-items-center justify-content-center h-100" for="${this.id}">
${ribbonHtml}
<h5 class="service-title mb-1">${this.title}</h5>
<p class="service-desc mb-1 small">${this.description}</p>
<h4 class="service-price fw-semibold mt-2 h4 mb-0">${this.price} ${this.priceUnit}</h4>
</label>
</div>
`);
const $checkbox = $card.find('input[type="checkbox"]');
if (!this.allowChange) {
// Deshabilita el cambio de estado visual
$checkbox.on('click', (e) => {
e.preventDefault();
e.stopImmediatePropagation();
return false;
});
}
return $card;
}
}
export default ServiceOptionCard;

View File

@ -0,0 +1,141 @@
export function updateEncuadernacion() {
const $selected = $('.tipo-libro.selected');
if ($selected.length > 0) {
const resumen = $selected.data('summary-text') || $selected.attr('id');
$('#summary-encuadernacion').text(resumen);
}
}
export function updateFormato(){
if($('#formato-personalizado').is(':checked')) {
$('#summary-formato').text($('#ancho').val() + 'x' + $('#alto').val() + ' mm');
} else {
const $selected = $('#formato option:selected');
const resumen = $selected.data('summary-text') || $selected.val();
$('#summary-formato').text(resumen + ' mm');
}
}
export function updatePaginas() {
const paginas = $('#paginas').val();
$('#summary-paginas').text(paginas );
const paginasColor = $('#paginas-color').val();
$('#summary-paginas-color').text(paginasColor );
const paginasNegro = $('#paginas-negro').val();
$('#summary-paginas-negro').text(paginasNegro );
}
export function updateTipoImpresion() {
const $selected = $('.opcion-color.selected');
if ($selected.length > 0) {
const resumen = $selected.data('summary-text') || $selected.attr('id');
$('#summary-tipo-interior').text(resumen);
}
}
export function updatePapelInterior() {
const $selected = $('.papel-interior.selected');
if ($selected.length > 0) {
const resumen = $selected.data('summary-text') || $selected.val();
$('#summary-papel-interior').text(resumen);
}
}
export function updateGramajeInterior() {
const gramaje = $('input[name="gramaje-interior"]:checked');
if(gramaje.length > 0) {
$('#summary-gramaje-interior').text(gramaje.data('gramaje'));
}
}
export function updateTapaCubierta(){
const $selected = $('.tapa-cubierta.selected');
if ($selected.length > 0) {
const resumen = $selected.data('summary-text') || $selected.attr('id');
$('#summary-tapa-cubierta').text(resumen);
}
if($selected.attr('id') === 'tapaBlanda') {
$('.tapa-blanda-row').removeClass('d-none');
$('.tapa-dura-row').addClass('d-none');
$('#summary-cubierta-solapas').text($('#sin-solapas').hasClass('selected') ? $('#sin-solapas').data('summary-text') : $('#con-solapas').data('summary-text'));
if($('#con-solapas').hasClass('selected')) {
$('#summary-tamanio-solapa-row').removeClass('d-none');
$('#summary-tamanio-solapa').text($('#tamanio-solapas-cubierta').val() + ' mm');
} else {
$('#summary-tamanio-solapa-row').addClass('d-none');
$('#summary-tamanio-solapa').text('');
}
$('#summary-impresion-cubierta-row').removeClass('d-none');
$('#summary-impresion-cubierta').text($('#impresion-cubierta option:selected').text());
}
else{
$('.tapa-blanda-row').addClass('d-none');
$('.tapa-dura-row').removeClass('d-none');
$('#summary-papel-guardas').text($('#papel-guardas option:selected').text());
$('#summary-guardas-impresas').text($('#guardas-impresas option:selected').text());
$('#summary-cabezada').text($('#cabezada option:selected').text());
}
}
export function updatePapelCubierta() {
const $selected = $('.papel-cubierta.selected');
if ($selected.length > 0) {
const resumen = $selected.data('summary-text') || $selected.val();
$('#summary-papel-cubierta').text(resumen);
}
}
export function updateGramajeCubierta() {
const gramaje = $('input[name="gramaje-cubierta"]:checked');
if(gramaje.length > 0) {
$('#summary-gramaje-cubierta').text(gramaje.data('gramaje'));
}
}
export function updateAcabadoCubierta() {
const acabado = $('input[name="acabado-cubierta"]:checked');
if(acabado.length > 0) {
let labelText = '';
const id = acabado.attr('id');
if (id) {
labelText = $(`label[for="${id}"]`).text().trim();
}
// Caso 2: input dentro de label
if (!labelText) {
labelText = acabado.closest('label').text().trim();
}
$('#summary-acabado-cubierta').text(labelText);
}
}
export function updateSobreCubierta() {
if($('#sobrecubierta').hasClass('active')) {
$('#summary-sobrecubierta-papel-gramaje').text($('#papel-sobrecubierta option:selected').text());
$('#summary-sobrecubierta-tamanio-solapa').text($('#tamanio-solapas-sobrecubierta').val() + ' mm');
$('#summary-sobrecubierta-acabado').text($('#sobrecubierta-acabado option:selected').text());
}
}
export function updateFaja() {
if($('#faja').hasClass('active')) {
$('#summary-faja-papel-gramaje').text($('#papel-faja option:selected').text());
$('#summary-faja-alto-faja').text($('#alto-faja').val() + ' mm');
$('#summary-faja-tamanio-solapa').text($('#tamanio-solapas-faja').val() + ' mm');
$('#summary-faja-acabado').text($('#faja-acabado option:selected').text());
}
}

View File

@ -0,0 +1,123 @@
// ===== Clase =====
class TiradaCard {
constructor({ id, titulo, unidades, precioUnidad, precioTotal, selected = false, moneda = '€', name = 'tirada',
labels = {}, locale = (document.documentElement.lang || navigator.language || 'es-ES')
}) {
this.id = id;
this.titulo = titulo;
this.unidades = unidades;
this.precioUnidad = precioUnidad;
this.precioTotal = precioTotal;
this.selected = selected;
this.moneda = moneda;
this.name = name;
this.locale = locale;
this.labels = Object.assign({
perUnit: 'Precio por unidad',
total: 'Total',
select: 'Seleccionar tirada',
selected: 'Seleccionada',
units: 'UNIDADES'
}, labels);
this.$container = null; // se establece al renderizar
}
#formatMoneyES(value, decimals = 2) {
return new Intl.NumberFormat(this.locale, {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals
}).format(Number(value));
}
#title() {
if (this.titulo) return this.titulo;
if (this.unidades != null) return `${this.unidades} ${this.labels.units}`;
return '';
}
// pásale el contenedor donde van todas las tarjetas de este grupo
renderCol($container) {
this.$container = $container ? $($container) : $(document.body);
const col = $(`
<div class="col d-flex">
<label class="tirada-card ${this.selected ? 'selected' : ''} w-100 h-100" for="tirada-${this.id}">
<input type="radio" name="${this.name}" id="tirada-${this.id}" value="${this.unidades}" ${this.selected ? 'checked' : ''}>
<div class="title">${this.#title()}</div>
<div class="per muted">${this.labels.perUnit}</div>
<div class="price-big">${this.#formatMoneyES(this.precioUnidad, 4)} ${this.moneda}</div>
<div class="price-row">
<div class="muted">${this.labels.total}</div>
<div class="fw-bold">${this.#formatMoneyES(this.precioTotal, 2)} ${this.moneda}</div>
</div>
<button type="button" class="btn btn-select-tirada">${this.labels.select}</button>
</label>
</div>
`);
// --- Delegación de eventos en el contenedor (una sola vez por grupo) ---
const boundKey = `tirada-bound-${this.name}`;
const groupName = this.name;
const $group = this.$container;
const labels = this.labels;
if (!$group.data(boundKey)) {
$group.on('change', `input[type="radio"][name="${groupName}"]`, function () {
// radios del grupo
const $groupRadios = $group.find(`input[type="radio"][name="${groupName}"]`);
// resetear todas las tarjetas del grupo
$groupRadios
.closest('.tirada-card')
.removeClass('selected')
.find('.btn-select-tirada')
.html(labels.select || 'Seleccionar tirada');
// marcar la tarjeta seleccionada y su botón
const $card = $(this).closest('.tirada-card');
$card.find('.btn-select-tirada')
.html(labels.selected + ' <i class="mdi mdi-check-circle ms-1"></i>');
$card.addClass('selected');
const $input = $(this);
const detail = {
id: $input.attr('id'),
name: $input.attr('name'),
unidades: Number($input.data('unidades') ?? $input.val()),
precioUnidad: Number($input.data('precioUnidad')),
precioTotal: Number($input.data('precioTotal'))
};
// jQuery: pasa 'detail' como segundo argumento del handler
$group.trigger('tirada:changed', [detail]);
});
$group.data(boundKey, true);
}
const $btnLocal = col.find('.btn-select-tirada');
if (this.selected) {
$btnLocal.html(labels.selected + ' <i class="mdi mdi-check-circle ms-1"></i>');
}
// --- Comportamiento de la tarjeta/botón (local a esta tarjeta) ---
const card = col.find('.tirada-card');
col.find('.btn-select-tirada').on('click', (e) => {
e.preventDefault();
card.find('input[type="radio"]').prop('checked', true).trigger('change');
});
card.on('click', function (e) {
if ($(e.target).is('.btn-select-tirada')) return;
$(this).find('input[type="radio"]').prop('checked', true).trigger('change');
});
return col;
}
}
export default TiradaCard;

View File

@ -247,7 +247,6 @@ if (document.getElementById("sa-position"))
icon: 'success',
title: 'Your work has been saved',
showConfirmButton: false,
timer: 1500,
showCloseButton: true
})
});

View File

@ -1,15 +1,18 @@
/*
Template Name: Velzon - Admin & Dashboard Template
Author: Themesbrand
Version: 2.4.0
Website: https://Themesbrand.com/
Contact: Themesbrand@gmail.com
File: Common Plugins Js File
*/
(function () {
const head = document.head || document.getElementsByTagName("head")[0];
//Common plugins
if(document.querySelectorAll("[toast-list]") || document.querySelectorAll('[data-choices]') || document.querySelectorAll("[data-provider]")){
document.writeln("<script type='text/javascript' src='https://cdn.jsdelivr.net/npm/toastify-js'></script>");
document.writeln("<script type='text/javascript' src='/assets/libs/choices.js/public/assets/scripts/choices.min.js'></script>");
document.writeln("<script type='text/javascript' src='/assets/libs/flatpickr/flatpickr.min.js'></script>");
}
const scripts = [
"/assets/libs/toastify-js/src/toastify.js",
"/assets/libs/choices.js/public/assets/scripts/choices.min.js",
"/assets/libs/flatpickr/flatpickr.min.js",
"/assets/libs/feather-icons/feather.min.js" // <- AÑADIMOS feather aquí
];
scripts.forEach(src => {
const script = document.createElement("script");
script.src = src;
script.type = "text/javascript";
script.defer = true;
head.appendChild(script);
});
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,46 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{imprimelibros/layout}">
<head>
<th:block layout:fragment="pagetitle" />
<th:block th:replace="~{imprimelibros/partials/head-css :: head-css}" />
<th:block layout:fragment="pagecss">
<link th:href="@{/assets/css/presupuestador.css}" rel="stylesheet"
th:unless="${#authorization.expression('isAuthenticated()')}" />
</th:block>
</head>
<body>
<div th:replace="~{imprimelibros/partials/topbar :: topbar}" />
<div th:if="${#authorization.expression('isAuthenticated()')}">
<div th:replace="~{imprimelibros/partials/sidebar :: sidebar}" />
</div>
<th:block layout:fragment="content">
<div th:if="${#authorization.expression('isAuthenticated()')}">
<div class="container-fluid">
<!-- contenido para usuario logueado -->
</div>
</div>
<div th:unless="${#authorization.expression('isAuthenticated()')}">
<div th:insert="~{imprimelibros/presupuestos/presupuestador :: presupuestador}"></div>
</div>
</th:block>
<th:block layout:fragment="modal" />
<th:block th:replace="~{theme/partials/vendor-scripts :: scripts}" />
<th:block layout:fragment="pagejs">
<div th:unless="${#authorization.expression('isAuthenticated()')}">
<script th:src="@{/assets/js/pages/imprimelibros/presupuestador/imagen-selector.js}"></script>
<script type="module" th:src="@{/assets/js/pages/imprimelibros/presupuestador/presupuestador.js}"></script>
</div>
<script th:inline="javascript">
window.languageBundle = /*[[${languageBundle}]]*/ {};
</script>
</th:block>
</body>
</html>

View File

@ -0,0 +1,39 @@
<html th:lang="${#locale.language}" th:with="isAuth=${#authorization.expression('isAuthenticated()')}"
th:attrappend="data-layout=${isAuth} ? 'semibox' : 'horizontal'" data-sidebar-visibility="show" data-topbar="light"
data-sidebar="light" data-sidebar-size="lg" data-sidebar-image="none" data-preloader="disable"
xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<th:block layout:fragment="pagetitle" />
<th:block th:replace="~{imprimelibros/partials/head-css :: head-css}" />
<link href="/assets/libs/sweetalert2/sweetalert2.min.css" rel="stylesheet" type="text/css" />
<link href="/assets/libs/select2/select2.min.css" rel="stylesheet" />
<th:block layout:fragment="pagecss" />
</head>
<body>
<div th:replace="~{imprimelibros/partials/topbar :: topbar}" />
<div th:if="${#authorization.expression('isAuthenticated()')}">
<div th:replace="~{imprimelibros/partials/sidebar :: sidebar}" />
</div>
<section class="main-content">
<div class="page-content">
<div class="container-fluid">
<section layout:fragment="content" th:remove="tag"></section>
</div>
</div>
<div th:replace="~{imprimelibros/partials/footer :: footer}" />
</section>
<th:block layout:fragment="modal" />
<th:block th:replace="~{theme/partials/vendor-scripts :: scripts}" />
<script src="/assets/libs/jquery/jquery-3.6.0.min.js"></script>
<script src="/assets/libs/sweetalert2/sweetalert2.min.js"></script>
<script src="/assets/libs/select2/select2.min.js"></script>
<th:block layout:fragment="pagejs" />
<script th:src="@{/assets/js/app.js}"></script>
<script th:src="@{/assets/js/pages/imprimelibros/languageBundle.js}"></script>
</body>
</html>

View File

@ -0,0 +1,16 @@
<div th:fragment="footer" th:remove="tag">
<footer class="footer">
<div class="container-fluid">
<div class="row">
<div class="col-sm-6">
<span th:text="${T(java.time.Year).now()}">2025</span> © Imprime Libros.
</div>
<div class="col-sm-6">
<div class="text-sm-end d-none d-sm-block">
Design & Develop by IMN & JJO
</div>
</div>
</div>
</div>
</footer>
</div>

View File

@ -0,0 +1,14 @@
<div th:fragment="head-css" th:remove="tag">
<!-- Layout config Js -->
<script src="/assets/js/layout.js"></script>
<!-- Bootstrap Css -->
<link href="/assets/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<!-- Icons Css -->
<link href="/assets/css/icons.min.css" rel="stylesheet" type="text/css" />
<!-- App Css-->
<link href="/assets/css/app.min.css" rel="stylesheet" type="text/css" />
<!-- custom Css-->
<link href="/assets/css/custom.min.css" rel="stylesheet" type="text/css" />
<link href="/assets/css/imprimelibros.css" rel="stylesheet" type="text/css" />
</div>

View File

@ -0,0 +1,26 @@
<div th:fragment="page-title(title,pagetitle)" th:remove="tag">
<!-- start page title -->
<div class="row">
<div class="col-12">
<div class="page-title-box d-sm-flex align-items-center justify-content-between">
<h4 th:data-key="${keyTitle}" class="mb-sm-0" th:text="${currentPage}"></h4>
<div class="page-title-right">
<ol class="breadcrumb m-0">
<li th:each="item : ${breadcrumb}" class="breadcrumb-item"
th:classappend="${item.url == null}? 'active' : ''">
<a th:if="${item.url != null}" th:data-key="${item.dataKey}" th:href="${item.url}"
th:text="${item.label}"></a>
<span th:if="${item.url == null}" th:data-key="${item.dataKey}"
th:text="${item.label}"></span>
</li>
</ol>
</div>
</div>
</div>
</div>
<!-- end page title -->
</div>

View File

@ -0,0 +1,53 @@
<div th:fragment="sidebar" th:remove="tag">
<!-- ========== App Menu ========== -->
<div class="app-menu navbar-menu">
<!-- LOGO -->
<div class="navbar-brand-box">
<!-- Dark Logo-->
<a href="/" class="logo logo-dark">
<span class="logo-sm">
<img src="/assets/images/logo-sm.png" alt="" height="22">
</span>
<span class="logo-lg">
<img src="/assets/images/logo-dark.png" alt="" height="17">
</span>
</a>
<!-- Light Logo-->
<a href="/" class="logo logo-light">
<span class="logo-sm">
<img src="/assets/images/logo-sm.png" alt="" height="22">
</span>
<span class="logo-lg">
<img src="/assets/images/logo-light.png" alt="" height="17">
</span>
</a>
<button type="button" class="btn btn-sm p-0 fs-20 header-item float-end btn-vertical-sm-hover"
id="vertical-hover">
<i class="ri-record-circle-line"></i>
</button>
</div>
<div id="scrollbar">
<div class="container-fluid">
<div id="two-column-menu">
</div>
<li href="/" class="menu-title"><span data-key="t-menu">Menu</span></li>
<ul class="navbar-nav" id="navbar-nav">
<li class="nav-item">
<a class="nav-link menu-link" href="/">
<i class="ri-home-line"></i> <span data-key="t-home">Inicio</span>
</a>
</li>
<!-- <div th:replace="~{printhub/partials/sidebarMenus/configurationMenu :: configuration}"></div> -->
</ul>
</div>
<!-- Sidebar -->
</div>
<div class="sidebar-background"></div>
</div>
<!-- Left Sidebar End -->
<!-- Vertical Overlay-->
<div class="vertical-overlay"></div>
</div>

View File

@ -0,0 +1,16 @@
<div th:fragment="configuration" th:remove="tag">
<li class="nav-item">
<a class="nav-link menu-link" href="#configurationMenu" data-bs-toggle="collapse" role="button"
aria-expanded="false" aria-controls="configurationMenu">
<i class="ri-tools-line"></i> <span data-key="t-menu-config">Configuración</span>
</a>
<div class="collapse menu-dropdown" id="configurationMenu">
<ul class="nav nav-sm flex-column">
<li class="nav-item">
<a href="/configuration/printers" class="nav-link" data-key="t-menu-config-impresioras">
Impresoras </a>
</li>
</ul>
</div>
</li> <!-- end Dashboard Menu -->
</div>

Some files were not shown because too many files have changed in this diff Show More