Files
asistente/components/SliceEnd.vue
2025-11-14 14:38:45 +01:00

279 lines
9.0 KiB
Vue

<template>
<div class="space-y-4 bg-surface text-accent rounded-2xl px-4 sm:px-10 py-12 w-full max-w-2xl h-fit">
<section id="pdf-content" class="space-y-4">
<img src="../public/logo-bravio.png" alt="logo-bravio-blanco" class="h-12 md:h-14 object-cover hidden" />
<h2 class="text-3xl font-semibold text-center">{{ data.title[langcode] }} {{ universityName }}</h2>
<p class="text-center text-muted">{{ data.subtitle[langcode] }} <span class="font-bold text-white">{{ country }}</span></p>
<!-- <div class="text-center">
<h3 class="text-lg font-semibold my-4">Tu puntuación total es {{ totalScore?.score }}/{{ totalScore?.totalPossiblePoints }}</h3>
<p v-if="totalScore?.recommendations" class="text-lg font-semibold my-4">Recomendarión: {{ totalScore?.recommendations[langcode] }}</p>
</div> -->
<section v-for="(res, index) in results" :key="index" class="border border-border rounded-2xl">
<div v-if="res.results[0] !== 'SIN REGLA'" class="flex flex-col gap-2 justify-start items-center p-6 mb-4">
<div class="w-full flex flex-row justify-center items-center gap-2 border-b border-border pb-4">
<h3 class="font-semibold text-xl">{{ res.sectionTitle[langcode] }}</h3>
<img src="../assets/img/x-cross.png" alt="cruz roja" class="h-6 w-6">
<!-- <div class="flex gap-4 justify-end items-center w-full">
<p class="text-muted">Puntuación</p>
<div class="p-1 rounded-xl bg-[#DAB2F4] flex justify-center min-w-20"><p>{{ res.score }}/{{ res.sectionPossibleTotalPoints }}</p></div>
</div> -->
</div>
<div v-for="(item, index) in res.results" :key="index" class="flex flex-col">
<p v-if="item[langcode] !== ''" class="text-sm mb-2" v-html="item[langcode]"></p>
</div>
</div>
</section>
</section>
<p class="my-4 text-center">{{ langcode === 'es' ? 'Revisa la aplicación normativa con Asesoría Jurídica de tu universidad' : 'Reveja a aplicação normativa com a Assessoria Jurídica da sua universidade' }}</p>
<client-only>
<div class="flex justify-center gap-2 mt-4">
<ButtonCTA color="transparent" @click="reloadPage">
{{ langcode === 'es' ? 'Cerrar asistente' : 'Fechar assistente' }}
</ButtonCTA>
<ButtonCTA color="accent" @click="downloadResultsAsPDF">
{{ langcode === 'es' ? 'Descargar Informe (PDF)' : 'Baixar Relatório (PDF)' }}
</ButtonCTA>
</div>
</client-only>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: "SliceEnd",
props: {
data: {
type: Object,
default: () => ({})
},
currentIndex: { type: Number, default: 0 },
total: { type: Number, default: 1 }
},
emits: ['previous'],
data() {
return {
results: null,
totalScore: null,
recommendations: [],
universityName: '',
country: ''
}
},
computed: {
...mapGetters(['langcode']),
resultsByLanguage() {
if (!this.results) return []
return this.results.map(group => {
const filteredResults = group.results
.map(item => {
return item[this.langcode] ? { [this.langcode]: item[this.langcode] } : null
})
.filter(item => item !== null)
return {
...group,
results: filteredResults
}
})
}
},
mounted() {
this.getGroupedResults()
this.getUniversityAndCountry()
},
methods: {
getResults() {
this.results = JSON.parse(localStorage.getItem('results'))
this.totalScore = JSON.parse(localStorage.getItem('final-score'))
},
getUniversityAndCountry() {
const storedAnswers = localStorage.getItem("answers")
if (!storedAnswers) return
const parsed = JSON.parse(storedAnswers)
const uniAnswer = parsed.find(item => item.sliceId === "P1-01")
const countryAnswer = parsed.find(item => item.sliceId === "P1-02")
this.universityName = uniAnswer?.value?.university || ""
this.country = countryAnswer?.value || ""
},
getGroupedResults() {
const storedResults = localStorage.getItem("results")
if (!storedResults) return []
const results = JSON.parse(storedResults).filter(r => r.sectionId)
const grouped = {}
results.forEach(item => {
const groupId = item.sectionId.split("_")[0] || item.sectionId
if (!grouped[groupId]) {
grouped[groupId] = {
sectionGroup: groupId,
sectionTitle: item.sectionTitle,
results: [],
}
}
if (
item.result &&
(
(item.result.es && item.result.es.trim() !== "") ||
(item.result.pt && item.result.pt.trim() !== "")
)
) {
grouped[groupId].results.push(item.result)
}
})
this.results = Object.values(grouped).filter(
group => group.results.length > 0
)
localStorage.setItem("grouped-results", JSON.stringify(this.results))
},
reloadPage() {
if (import.meta.client) {
localStorage.clear()
window.location.reload()
}
},
async downloadResultsAsPDF() {
if (!this.results || !this.results.length) {
alert("No hay resultados para exportar.")
return
}
if (import.meta.server) return // Evitar SSR
// Carga dinámica segura de pdfMake y las fuentes
const pdfMakeModule = await import("pdfmake/build/pdfmake")
const pdfFontsModule = await import("pdfmake/build/vfs_fonts")
const pdfMake = pdfMakeModule.default || pdfMakeModule
const pdfFonts = pdfFontsModule.default || pdfFontsModule
if (pdfFonts.pdfMake && pdfFonts.pdfMake.vfs) {
pdfMake.vfs = pdfFonts.pdfMake.vfs
} else if (pdfFonts.vfs) {
pdfMake.vfs = pdfFonts.vfs
} else {
pdfMake.vfs = pdfFonts
}
const lang = this.langcode
const title =
lang === "es"
? "Informe de Recomendaciones"
: "Relatório de Recomendações"
const content = []
// Logo de Bravio
content.push({
image: await new Promise((resolve) => {
const img = new Image()
img.src = "/logo-bravio.png"
img.onload = () => {
const canvas = document.createElement("canvas")
canvas.width = img.width
canvas.height = img.height
const ctx = canvas.getContext("2d")
ctx.drawImage(img, 0, 0)
resolve(canvas.toDataURL("image/png"))
}
}),
width: 150,
alignment: "start",
margin: [0, 0, 0, 10],
})
// Encabezado
content.push({
text: `${title}\n${this.universityName}`,
style: "header",
alignment: "center",
margin: [0, 0, 0, 10],
})
content.push({
text: this.country,
alignment: "center",
margin: [0, 0, 0, 20],
italics: true,
})
content.push({
text:
lang === "es"
? "En base a las respuesta que nos ha facilitado en cada una de las áreas, le listamos una serie de recomendaciones que pueden ser de utilidad y guiarle a poner en marcha su defensoría, comisionado u oficina similar."
: "Com base nas respostas que você nos forneceu em cada uma das áreas, listamos uma série de recomendações que podem ser úteis e guiá-lo a iniciar sua defensoría, comissariado ou escritório similar.",
alignment: "start",
margin: [0, 0, 0, 10],
})
// Cuerpo del PDF
this.results.forEach((section) => {
content.push({
text: section.sectionTitle[lang],
style: "sectionTitle",
margin: [0, 10, 0, 5],
})
section.results.forEach((item, index) => {
const cleanText = item[lang]
.replace(/<br\s*\/?>/gi, "\n")
.replace(/✔/g, "")
.replace(/<\/?[^>]+(>|$)/g, "")
content.push({
text: `${index + 1}. ${cleanText}`,
style: "resultText",
margin: [0, 2, 0, 10],
})
})
})
// Objeto de definición del documento PDF
const docDefinition = {
pageSize: "A4",
pageMargins: [40, 60, 40, 60],
content,
styles: {
header: {
fontSize: 18,
bold: true,
color: "#4F2773",
},
sectionTitle: {
fontSize: 13,
bold: true,
color: "#4F2773",
margin: [0, 10, 0, 4],
},
resultText: {
fontSize: 12,
lineHeight: 1.3,
},
},
defaultStyle: {
font: "Roboto",
},
}
// Generar PDF solo si pdfMake está correctamente inicializado
try {
pdfMake.createPdf(docDefinition).download(`${title}_Bravioo.pdf`)
} catch (error) {
console.error("Error al generar PDF:", error)
}
}
}
}
</script>