feat: soporte multi-proceso para resultados de admisión por sedes

main
parent 677e28e26a
commit 9a546e699e

@ -109,7 +109,7 @@
<a-divider class="custom-divider" /> <a-divider class="custom-divider" />
<!-- Resultados (cuando hay archivos subidos) --> <!-- Resultados (cuando hay archivos subidos) -->
<div v-if="hayResultados" class="preinscripcion-section"> <div v-if="tieneResultados(proceso)" class="preinscripcion-section">
<div class="preinscripcion-info"> <div class="preinscripcion-info">
<h4 class="subheading">Resultados del Examen</h4> <h4 class="subheading">Resultados del Examen</h4>
<p>Consulta los resultados del proceso de admisión vigente</p> <p>Consulta los resultados del proceso de admisión vigente</p>
@ -126,8 +126,8 @@
</div> </div>
</div> </div>
<!-- Preinscripción (cuando no hay resultados) --> <!-- Preinscripción (solo dentro del periodo de inscripción y sin resultados) -->
<div v-else class="preinscripcion-section"> <div v-else-if="mostrarPreinscripcion(proceso)" class="preinscripcion-section">
<div class="preinscripcion-info"> <div class="preinscripcion-info">
<h4 class="subheading">Preinscripción en Línea</h4> <h4 class="subheading">Preinscripción en Línea</h4>
<p>Completa tu preinscripción de manera virtual y segura</p> <p>Completa tu preinscripción de manera virtual y segura</p>
@ -272,7 +272,14 @@ const store = useWebAdmisionStore()
const resultadosStore = useProcesoAdmisionResultadoStore() const resultadosStore = useProcesoAdmisionResultadoStore()
const router = useRouter() const router = useRouter()
const hayResultados = computed(() => resultadosStore.archivos.length > 0) const tieneResultados = (proceso) =>
(resultadosStore.archivosPorProceso[proceso.id]?.length ?? 0) > 0
const mostrarPreinscripcion = (proceso) => {
if (!proceso.link_preinscripcion) return false
if (tieneResultados(proceso)) return false
return new Date() <= new Date(proceso.fecha_fin_inscripcion)
}
const emit = defineEmits(["show-modal", "open-preinscripcion"]) const emit = defineEmits(["show-modal", "open-preinscripcion"])
@ -280,8 +287,10 @@ const modalVisible = ref(false)
const detallesSeleccionados = ref([]) const detallesSeleccionados = ref([])
const tituloModal = ref("") const tituloModal = ref("")
onMounted(() => { onMounted(async () => {
store.cargarProcesos() await store.cargarProcesos()
const ids = store.procesos.map(p => p.id)
if (ids.length) resultadosStore.fetchArchivosMultiples(ids)
}) })
const formatFecha = (fecha) => { const formatFecha = (fecha) => {

@ -53,7 +53,7 @@
<span>Resultados del:</span> <span>Resultados del:</span>
</div> </div>
<h3>{{ procesoPrincipal?.titulo ?? 'Proceso vigente' }}</h3> <h3>EXAMEN GENERAL 2026-I SEDES</h3>
<div <div
v-for="f in fechasExamen" v-for="f in fechasExamen"
:key="f.label" :key="f.label"
@ -107,17 +107,24 @@ const resultadosStore = useProcesoAdmisionResultadoStore()
const procesoPrincipal = computed(() => webStore.procesoPrincipal) const procesoPrincipal = computed(() => webStore.procesoPrincipal)
const hayResultados = computed(() => resultadosStore.archivos.length > 0) const todosLosArchivos = computed(() =>
const hayExamen1 = computed(() => { Object.values(resultadosStore.archivosPorProceso).flat()
return resultadosStore.archivos.some(a => a.orden === 1 || a.orden === 2) )
}) const hayResultados = computed(() => todosLosArchivos.value.length > 0)
const hayExamen2 = computed(() => { // Proceso cuyas fechas se muestran en la card
return resultadosStore.archivos.some(a => a.orden === 5 || a.orden === 6) const procesoFechas = computed(() =>
}) webStore.procesos.find(p => p.fecha_examen1) ?? procesoPrincipal.value
)
const hayExamen1 = computed(() =>
todosLosArchivos.value.some(a => a.orden === 1 || a.orden === 2)
)
const hayExamen2 = computed(() =>
todosLosArchivos.value.some(a => a.orden === 5 || a.orden === 6)
)
const fechasExamen = computed(() => { const fechasExamen = computed(() => {
const p = procesoPrincipal.value const p = procesoFechas.value
if (!p) return [] if (!p) return []
const fmt = (iso) => { const fmt = (iso) => {
@ -149,10 +156,8 @@ onMounted(async () => {
if (!webStore.procesos.length) { if (!webStore.procesos.length) {
await webStore.cargarProcesos() await webStore.cargarProcesos()
} }
const proceso = procesoPrincipal.value const ids = webStore.procesos.map(p => p.id)
if (proceso?.id) { if (ids.length) await resultadosStore.fetchArchivosMultiples(ids)
await resultadosStore.fetchArchivosPublico(proceso.id)
}
}) })
defineEmits(["scroll-to-convocatoria", "virtual-tour"]) defineEmits(["scroll-to-convocatoria", "virtual-tour"])

@ -10,26 +10,21 @@ const webStore = useWebAdmisionStore()
const resultadosStore = useProcesoAdmisionResultadoStore() const resultadosStore = useProcesoAdmisionResultadoStore()
onMounted(async () => { onMounted(async () => {
if (!webStore.procesos.length) { if (!webStore.procesos.length) {
await webStore.cargarProcesos() await webStore.cargarProcesos()
} }
const ids = webStore.procesos.map(p => p.id)
const proceso = webStore.procesoPrincipal if (ids.length) await resultadosStore.fetchArchivosMultiples(ids)
if (proceso?.id) {
await resultadosStore.fetchArchivosPublico(proceso.id)
}
}) })
const procesosConResultados = computed(() =>
const archivosExamen1 = computed(() => webStore.procesos
resultadosStore.archivos.filter(a => [1,2,3,4].includes(a.orden)) .map(p => ({
) proceso: p,
examen1: (resultadosStore.archivosPorProceso[p.id] ?? []).filter(a => [1,2,3,4].includes(a.orden)),
const archivosExamen2 = computed(() => examen2: (resultadosStore.archivosPorProceso[p.id] ?? []).filter(a => [5,6].includes(a.orden)),
resultadosStore.archivos.filter(a => [5,6].includes(a.orden)) }))
.filter(item => item.examen1.length || item.examen2.length)
) )
</script> </script>
@ -44,7 +39,7 @@ const archivosExamen2 = computed(() =>
<div class="section-header"> <div class="section-header">
<div class="header-with-badge"> <div class="header-with-badge">
<h2 class="section-title"> <h2 class="section-title">
RESULTADOS {{ webStore.procesoPrincipal?.titulo ?? 'Proceso vigente' }} RESULTADOS PROCESO DE ADMISIÓN 2026
</h2> </h2>
</div> </div>
@ -63,7 +58,7 @@ const archivosExamen2 = computed(() =>
</div> </div>
</template> </template>
<template v-else-if="!resultadosStore.archivos.length && !resultadosStore.loading"> <template v-else-if="!procesosConResultados.length && !resultadosStore.loading">
<div style="padding:6px 2px; color:#666;"> <div style="padding:6px 2px; color:#666;">
Resultados próximamente. Resultados próximamente.
</div> </div>
@ -71,139 +66,91 @@ const archivosExamen2 = computed(() =>
<template v-else> <template v-else>
<div style="padding:6px 2px; color:#666;"> <div style="padding:6px 2px; color:#666;">
Resultados disponibles {{ webStore.procesoPrincipal?.titulo }} Resultados disponibles.
</div> </div>
</template> </template>
</a-card> </a-card>
<!-- ================= UN BLOQUE POR PROCESO ================= -->
<!-- ================= EXAMEN 1 ================= --> <template v-for="item in procesosConResultados" :key="item.proceso.id">
<a-card
v-if="archivosExamen1.length" <!-- Examen 1 (sábado / slots 1-4) -->
class="year-section-card" <a-card v-if="item.examen1.length" class="year-section-card">
> <div class="year-header">
<div class="year-header"> <div class="year-icon"><FileSearchOutlined /></div>
<div class="year-icon"> <div>
<FileSearchOutlined /> <h3 class="year-title">{{ item.proceso.titulo }} Sábado</h3>
<p class="year-subtitle">
{{ item.proceso.fecha_examen1
? new Date(item.proceso.fecha_examen1)
.toLocaleDateString('es-PE', { day:'2-digit', month:'long', year:'numeric' })
: ''
}}
</p>
</div>
</div> </div>
<div> <a-divider class="custom-divider" />
<h3 class="year-title">
Resultados Sábado - Primera Etapa. <div class="secondary-list one-col">
</h3> <a-card
v-for="archivo in item.examen1"
<p class="year-subtitle"> :key="archivo.id"
{{ webStore.procesoPrincipal?.fecha_examen1 class="secondary-convocatoria-card"
? new Date(webStore.procesoPrincipal.fecha_examen1) >
.toLocaleDateString('es-PE', { day:'2-digit', month:'long', year:'numeric' }) <div class="convocatoria-header">
: '' <h4 class="secondary-title">{{ archivo.nombre }}</h4>
}} <a-tag class="status-tag" color="blue">Publicado</a-tag>
</p> </div>
<div class="card-footer">
<a-button type="primary" ghost size="small" :href="archivo.archivo_url" target="_blank">
<template #icon><FileSearchOutlined /></template>
Ver Resultados
</a-button>
</div>
</a-card>
</div> </div>
</div> </a-card>
<a-divider class="custom-divider" /> <!-- Examen 2 (domingo / slots 5-6) -->
<a-card v-if="item.examen2.length" class="year-section-card">
<div class="secondary-list one-col"> <div class="year-header">
<div class="year-icon"><FileSearchOutlined /></div>
<a-card <div>
v-for="archivo in archivosExamen1" <h3 class="year-title">{{ item.proceso.titulo }} Domingo</h3>
:key="archivo.id" <p class="year-subtitle">
class="secondary-convocatoria-card" {{ item.proceso.fecha_examen2
> ? new Date(item.proceso.fecha_examen2)
<div class="convocatoria-header"> .toLocaleDateString('es-PE', { day:'2-digit', month:'long', year:'numeric' })
<h4 class="secondary-title"> : ''
{{ archivo.nombre }} }}
</h4> </p>
<a-tag class="status-tag" color="blue">
Publicado
</a-tag>
</div> </div>
<div class="card-footer">
<a-button
type="primary"
ghost
size="small"
:href="archivo.archivo_url"
target="_blank"
>
<template #icon>
<FileSearchOutlined />
</template>
Ver Resultados
</a-button>
</div>
</a-card>
</div>
</a-card>
<a-card
v-if="archivosExamen2.length"
class="year-section-card"
>
<div class="year-header">
<div class="year-icon">
<FileSearchOutlined />
</div> </div>
<div> <a-divider class="custom-divider" />
<h3 class="year-title">
Resultados Domingo - Segunda Etapa. <div class="secondary-list one-col">
</h3> <a-card
v-for="archivo in item.examen2"
<p class="year-subtitle"> :key="archivo.id"
{{ webStore.procesoPrincipal?.fecha_examen2 class="secondary-convocatoria-card"
? new Date(webStore.procesoPrincipal.fecha_examen2) >
.toLocaleDateString('es-PE', { day:'2-digit', month:'long', year:'numeric' }) <div class="convocatoria-header">
: '' <h4 class="secondary-title">{{ archivo.nombre }}</h4>
}} <a-tag class="status-tag" color="blue">Publicado</a-tag>
</p> </div>
<div class="card-footer">
<a-button type="primary" ghost size="small" :href="archivo.archivo_url" target="_blank">
<template #icon><FileSearchOutlined /></template>
Ver Resultados
</a-button>
</div>
</a-card>
</div> </div>
</div> </a-card>
<a-divider class="custom-divider" />
<div class="secondary-list one-col">
<a-card
v-for="archivo in archivosExamen2"
:key="archivo.id"
class="secondary-convocatoria-card"
>
<div class="convocatoria-header">
<h4 class="secondary-title">
{{ archivo.nombre }}
</h4>
<a-tag class="status-tag" color="blue">
Publicado
</a-tag>
</div>
<div class="card-footer"> </template>
<a-button
type="primary"
ghost
size="small"
:href="archivo.archivo_url"
target="_blank"
>
<template #icon>
<FileSearchOutlined />
</template>
Ver Resultados
</a-button>
</div>
</a-card>
</div>
</a-card>
</div> </div>
</section> </section>

@ -43,11 +43,25 @@ export function generarNombreSlot(orden, proceso) {
export const useProcesoAdmisionResultadoStore = defineStore('procesoAdmisionResultado', { export const useProcesoAdmisionResultadoStore = defineStore('procesoAdmisionResultado', {
state: () => ({ state: () => ({
archivos: [], archivos: [],
archivosPorProceso: {},
loading: false, loading: false,
error: null, error: null,
}), }),
actions: { actions: {
// Uso público multi-proceso: HeroSection + ConvocatoriasSection + ProcesoResultado
async fetchArchivosMultiples(procesoIds) {
this.loading = true
const results = await Promise.allSettled(
procesoIds.map(id => apiPublico.get(`/proceso-resultado/${id}/archivos`))
)
procesoIds.forEach((id, i) => {
this.archivosPorProceso[id] =
results[i].status === 'fulfilled' ? (results[i].value.data ?? []) : []
})
this.loading = false
},
// Uso público: HeroSection + ProcesoResultado.vue // Uso público: HeroSection + ProcesoResultado.vue
async fetchArchivosPublico(procesoId) { async fetchArchivosPublico(procesoId) {
this.loading = true this.loading = true

Loading…
Cancel
Save