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" />
<!-- Resultados (cuando hay archivos subidos) -->
<div v-if="hayResultados" class="preinscripcion-section">
<div v-if="tieneResultados(proceso)" class="preinscripcion-section">
<div class="preinscripcion-info">
<h4 class="subheading">Resultados del Examen</h4>
<p>Consulta los resultados del proceso de admisión vigente</p>
@ -126,8 +126,8 @@
</div>
</div>
<!-- Preinscripción (cuando no hay resultados) -->
<div v-else class="preinscripcion-section">
<!-- Preinscripción (solo dentro del periodo de inscripción y sin resultados) -->
<div v-else-if="mostrarPreinscripcion(proceso)" class="preinscripcion-section">
<div class="preinscripcion-info">
<h4 class="subheading">Preinscripción en Línea</h4>
<p>Completa tu preinscripción de manera virtual y segura</p>
@ -272,7 +272,14 @@ const store = useWebAdmisionStore()
const resultadosStore = useProcesoAdmisionResultadoStore()
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"])
@ -280,8 +287,10 @@ const modalVisible = ref(false)
const detallesSeleccionados = ref([])
const tituloModal = ref("")
onMounted(() => {
store.cargarProcesos()
onMounted(async () => {
await store.cargarProcesos()
const ids = store.procesos.map(p => p.id)
if (ids.length) resultadosStore.fetchArchivosMultiples(ids)
})
const formatFecha = (fecha) => {

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

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

@ -43,11 +43,25 @@ export function generarNombreSlot(orden, proceso) {
export const useProcesoAdmisionResultadoStore = defineStore('procesoAdmisionResultado', {
state: () => ({
archivos: [],
archivosPorProceso: {},
loading: false,
error: null,
}),
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
async fetchArchivosPublico(procesoId) {
this.loading = true

Loading…
Cancel
Save