terminados primeras modificaciones

This commit is contained in:
2026-02-26 12:11:21 +01:00
parent 27eabde40f
commit bc09810d30
12 changed files with 2792 additions and 32 deletions

View File

@ -34,6 +34,7 @@ import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import jakarta.validation.Validator;
@ -151,7 +152,8 @@ public class PresupuestoController {
return ResponseEntity.badRequest().body(errores);
}
Map<String, Object> resultado = new HashMap<>();
Map<String , Object> datosInterior = apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto), locale);
Map<String, Object> datosInterior = apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto),
locale);
resultado.put("solapas", datosInterior.get("maxSolapas"));
resultado.put("lomo", datosInterior.get("lomo"));
resultado.putAll(presupuestoService.obtenerOpcionesAcabadosCubierta(presupuesto, locale));
@ -273,7 +275,8 @@ public class PresupuestoController {
}
}
Map<String , Object> datosInterior = apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto), locale);
Map<String, Object> datosInterior = apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto),
locale);
resultado.put("solapas", datosInterior.get("maxSolapas"));
resultado.put("lomo", datosInterior.get("lomo"));
@ -309,10 +312,11 @@ public class PresupuestoController {
presupuesto.setGramajeInterior(Integer.parseInt(opciones.get(0))); // Asignar primera opción
}
}
Map<String , Object> datosInterior = apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto), locale);
Map<String, Object> datosInterior = apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto),
locale);
resultado.put("solapas", datosInterior.get("maxSolapas"));
resultado.put("lomo", datosInterior.get("lomo"));
return ResponseEntity.ok(resultado);
}
@ -335,10 +339,11 @@ public class PresupuestoController {
}
Map<String, Object> resultado = new HashMap<>();
Map<String , Object> datosInterior = apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto), locale);
Map<String, Object> datosInterior = apiClient.getMaxSolapas(presupuestoService.toSkApiRequest(presupuesto),
locale);
resultado.put("solapas", datosInterior.get("maxSolapas"));
resultado.put("lomo", datosInterior.get("lomo"));
return ResponseEntity.ok(resultado);
}
@ -650,8 +655,7 @@ public class PresupuestoController {
"presupuesto.reimprimir.success.title",
"presupuesto.reimprimir.success.text",
"presupuesto.reimprimir.error.title",
"presupuesto.reimprimir.error.internal"
);
"presupuesto.reimprimir.error.internal");
Map<String, String> translations = translationService.getTranslations(locale, keys);
model.addAttribute("languageBundle", translations);
@ -676,7 +680,7 @@ public class PresupuestoController {
"presupuesto.add.error.save.title",
"presupuesto.iva-reducido",
"presupuesto.iva-reducido-descripcion",
"presupuesto.duplicar.title",
"presupuesto.duplicar.title",
"presupuesto.duplicar.text",
"presupuesto.duplicar.confirm",
"presupuesto.duplicar.cancelar",
@ -908,11 +912,11 @@ public class PresupuestoController {
return ResponseEntity.badRequest().body(errores);
}
try{
try {
Map<String, Object> lomos = presupuestoService.obtenerLomos(presupuesto);
presupuesto.setLomo(Double.valueOf(lomos.get("lomoInterior").toString()));
presupuesto.setLomoCubierta(Double.valueOf(lomos.get("lomoCubierta").toString()));
}catch(Exception ex){
} catch (Exception ex) {
log.error("Error al obtener lomos desde SK API", ex);
}
@ -941,6 +945,30 @@ public class PresupuestoController {
}
}
@GetMapping("/api/plantilla-cubierta.png")
public ResponseEntity<byte[]> getPlantillaCubierta(
@RequestParam("tipo") String tipoLibro,
@RequestParam("tapa") String tapa,
@RequestParam("ancho") Integer ancho,
@RequestParam("alto") Integer alto,
@RequestParam("lomo") Integer lomo,
@RequestParam("solapas") Integer solapas,
Locale locale) {
Presupuesto.TipoEncuadernacion tipoEncuadernacion = Presupuesto.TipoEncuadernacion.valueOf(tipoLibro);
Presupuesto.TipoCubierta tipoCubierta = Presupuesto.TipoCubierta.valueOf(tapa);
byte[] png = presupuestoService.obtenerPlantillaCubierta(tipoEncuadernacion, tipoCubierta, ancho, alto, lomo,
solapas, locale);
if (png == null)
return ResponseEntity.notFound().build();
return ResponseEntity.ok()
.cacheControl(CacheControl.noCache())
.contentType(MediaType.IMAGE_PNG)
.body(png);
}
@PostMapping("/{id}/comentario")
@ResponseBody
public String actualizarComentario(@PathVariable Long id,
@ -952,18 +980,17 @@ public class PresupuestoController {
@PostMapping("/api/duplicar/{id}")
@ResponseBody
public Map<String, Object> duplicarPresupuesto(
@PathVariable Long id,
@RequestParam(name = "titulo", defaultValue = "") String titulo) {
@PathVariable Long id,
@RequestParam(name = "titulo", defaultValue = "") String titulo) {
Long entity = presupuestoService.duplicarPresupuesto(id, titulo);
return Map.of("id", entity);
}
@PostMapping("/api/reimprimir/{id}")
@ResponseBody
public Map<String, Object> reimprimirPresupuesto(@PathVariable Long id) {
Long entity = presupuestoService.reimprimirPresupuesto(id);
return Map.of("id", entity);
}

View File

@ -6,6 +6,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.Locale;
@ -42,6 +44,15 @@ import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatricesReposito
import com.imprimelibros.erp.presupuesto.marcapaginas.MarcapaginasRepository;
import com.imprimelibros.erp.users.UserDao;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.springframework.core.io.ClassPathResource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.NumberFormat;
import jakarta.servlet.http.HttpServletRequest;
import com.imprimelibros.erp.externalApi.skApiClient;
@ -1300,7 +1311,7 @@ public class PresupuestoService {
public Map<String, Object> obtenerLomos(Presupuesto presupuesto) {
try {
Map<String, Object> response = apiClient.getLomos(this.toSkApiRequest(presupuesto));
return response;
} catch (Exception e) {
System.out.println("Error obteniendo lomos: " + e.getMessage());
@ -1308,6 +1319,65 @@ public class PresupuestoService {
}
}
public byte[] obtenerPlantillaCubierta(
Presupuesto.TipoEncuadernacion tipoLibro,
Presupuesto.TipoCubierta tapa,
Integer ancho,
Integer alto,
Integer lomo,
Integer solapas,
Locale locale) {
try {
String plantillaName = "plantilla";
if (tipoLibro == Presupuesto.TipoEncuadernacion.grapado) {
plantillaName += "-grapado";
}
if (solapas > 0) {
plantillaName += "-solapas";
}
plantillaName += ".svg";
Integer sangrado = 5; // mm,
if (tapa != Presupuesto.TipoCubierta.tapaBlanda && tipoLibro != Presupuesto.TipoEncuadernacion.grapado) {
sangrado = 20;
}
Integer ancho_t = lomo + sangrado * 2 + ancho * 2 + solapas * 2;
Integer alto_t = alto + sangrado * 2;
// 3) Leer SVG template como texto
String basePath = "static/assets/images/imprimelibros/presupuestador/templates-cubierta/";
String svg = readClasspathText(basePath + plantillaName);
// 4) Sustituciones {{...}}
Map<String, String> vars = new HashMap<>();
NumberFormat nf = NumberFormat.getIntegerInstance(locale);
vars.put("ancho", nf.format(ancho)); // mm o lo que representes
vars.put("alto", nf.format(alto));
vars.put("ancho_t", nf.format(ancho_t));
vars.put("alto_t", nf.format(alto_t));
vars.put("lomo_t", nf.format(lomo != null ? lomo : 0));
vars.put("solapa_t", nf.format(solapas != null ? solapas : 0));
vars.put("sangrado", nf.format(sangrado));
vars.put("portada", messageSource.getMessage("presupuesto.plantilla-cubierta.portada", null, locale));
vars.put("contraportada",
messageSource.getMessage("presupuesto.plantilla-cubierta.contraportada", null, locale));
vars.put("lomo", messageSource.getMessage("presupuesto.plantilla-cubierta.lomo", null, locale));
vars.put("solapa", messageSource.getMessage("presupuesto.plantilla-cubierta.solapa", null, locale));
svg = replaceMustache(svg, vars);
// 5) Render SVG -> PNG (Batik)
svg = sanitizeForBatik(svg);
return svgToPng(svg, /* dpi */ 300f);
} catch (Exception e) {
System.err.println("Error obteniendo plantilla de cubierta: " + e.getMessage());
return null;
}
}
/**
* PRIVADO (futuro botón "Guardar"): persiste el presupuesto como borrador.
*/
@ -1551,4 +1621,77 @@ public class PresupuestoService {
return ip;
}
private static String readClasspathText(String path) throws IOException {
ClassPathResource res = new ClassPathResource(path);
try (InputStream in = res.getInputStream()) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
}
}
private static String replaceMustache(String svg, Map<String, String> vars) {
String out = svg;
for (var entry : vars.entrySet()) {
out = out.replace("{{" + entry.getKey() + "}}", entry.getValue());
}
return out;
}
private static byte[] svgToPng(String svgXml, float dpi) throws Exception {
PNGTranscoder t = new PNGTranscoder();
// Esto SÍ es correcto (convierte unidades a mm según DPI)
t.addTranscodingHint(PNGTranscoder.KEY_PIXEL_UNIT_TO_MILLIMETER, 25.4f / dpi);
// ❌ NO uses KEY_AOI con null (provoca tu error)
// t.addTranscodingHint(PNGTranscoder.KEY_AOI, null);
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
TranscoderInput input = new TranscoderInput(new StringReader(svgXml));
TranscoderOutput output = new TranscoderOutput(out);
t.transcode(input, output);
return out.toByteArray();
}
}
private static String sanitizeForBatik(String svg) {
String out = svg;
// 1) Batik: context-stroke/context-fill
out = out.replace("context-stroke", "#000");
out = out.replace("context-fill", "none");
// 2) Batik: auto-start-reverse
out = out.replace("auto-start-reverse", "auto");
// 3) Reemplazar markers Triangle*/marker6* -> Arrow2S*
out = replaceMarkerAttr(out, "marker-start", "Arrow2Sstart");
out = replaceMarkerAttr(out, "marker-end", "Arrow2Send");
// 4) Lo MISMO pero cuando viene dentro de style="...marker-start:url(#X);..."
out = replaceMarkerInStyle(out, "marker-start", "Arrow2Sstart");
out = replaceMarkerInStyle(out, "marker-end", "Arrow2Send");
return out;
}
private static String replaceMarkerAttr(String svg, String attr, String newId) {
// Soporta: marker-start="url(#Triangle-5)" o marker-start='url( #Triangle-5 )'
Pattern p = Pattern.compile(
"(" + attr
+ "\\s*=\\s*)([\"'])\\s*url\\(\\s*#(Triangle[^\\s\\)\"']*|marker6[^\\s\\)\"']*)\\s*\\)\\s*\\2",
Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(svg);
return m.replaceAll("$1$2url(#" + newId + ")$2");
}
private static String replaceMarkerInStyle(String svg, String prop, String newId) {
// Soporta dentro de style: marker-start:url(#Triangle-5) (con espacios
// opcionales)
Pattern p = Pattern.compile(
"(" + prop + "\\s*:\\s*)url\\(\\s*#(Triangle[^\\s\\)\"';]*|marker6[^\\s\\)\"';]*)\\s*\\)",
Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(svg);
return m.replaceAll("$1url(#" + newId + ")");
}
}