You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

610 lines
15 KiB
Vue

2 months ago
<!-- components/process/ProcessSection.vue -->
<template>
2 months ago
<section class="process-section" aria-labelledby="process-title">
2 months ago
<div class="section-container">
<div class="section-header">
<!-- <h2 id="process-title" class="section-title">{{ tituloProceso }}</h2> -->
<h2 id="process-title" class="section-title">EXAMEN GENERAL 2026 - I SEDES</h2>
2 months ago
<p class="section-subtitle">
2 months ago
¿No sabes por dónde empezar? Aquí te guiamos paso a paso y te decimos qué debes hacer hoy.
2 months ago
</p>
</div>
2 months ago
<div class="process-card">
2 months ago
<!-- STEPS -->
2 months ago
<a-steps
:direction="isMobile ? 'vertical' : 'horizontal'"
:responsive="false"
class="modern-steps"
2 months ago
:items="stepsItems"
/>
<!-- GUÍA PARA POSTULANTES -->
<div class="help-box" v-if="store.procesoPrincipal">
2 months ago
<div class="help-title"> Guía rápida (para no perderte)</div>
2 months ago
<div class="help-grid">
<!-- Etapas activas -->
<div class="help-item">
<div class="help-label">1) ¿Qué etapa está activa hoy?</div>
<div class="badges">
<span v-if="active.pre" class="badge badge-blue">
Preinscripción activa (virtual / en línea)
</span>
<span v-if="active.ins" class="badge badge-blue">
Inscripción activa (presencial en el lugar indicado por las sedes)
2 months ago
</span>
<span v-if="active.exa" class="badge badge-green">
Hoy es el Examen
</span>
<span v-if="active.res" class="badge badge-green">
Hoy salen Resultados
</span>
<span v-if="active.bio" class="badge badge-orange">
Biométrico activo (solo ingresantes)
</span>
<span v-if="noActive" class="badge badge-gray">
Aún no inicia o ya terminó una etapa. Revisa las fechas del proceso.
</span>
</div>
<div class="tiny-hint">
2 months ago
Si ves 🟢 en una fecha, significa que esa etapa está activa hoy.
2 months ago
</div>
</div>
<div class="help-item">
<div class="help-label">2) ¿Qué debo hacer ahora?</div>
<ul class="help-list">
<li v-for="(t, i) in tareasHoy" :key="i">{{ t }}</li>
</ul>
<div class="help-actions">
<!-- Preinscripción Virtual (solo si existe link) -->
<a-button
v-if="store.procesoPrincipal.link_preinscripcion"
type="primary"
:href="store.procesoPrincipal.link_preinscripcion"
target="_blank"
>
Iniciar Preinscripción
</a-button>
<a-button
v-if="store.procesoPrincipal.link_reglamento"
type="default"
:href="store.procesoPrincipal.link_reglamento"
target="_blank"
>
Ver Reglamento
</a-button>
<a-button
v-if="store.procesoPrincipal.link_resultados"
type="default"
:href="store.procesoPrincipal.link_resultados"
target="_blank"
>
Ver Resultados
</a-button>
</div>
</div>
</div>
<!-- Nota fija sobre inscripción presencial -->
<div class="campus-note">
2 months ago
<b>Importante:</b> La <b>Inscripción</b> se realiza de forma <b>presencial</b> en el
2 months ago
<b>Campus Universitario</b>. Lleva tu DNI y los requisitos solicitados.
</div>
</div>
<div class="process-note" v-else>
2 months ago
<span class="dot" />
2 months ago
<span>Cargando proceso...</span>
2 months ago
</div>
</div>
2 months ago
</div>
</section>
</template>
2 months ago
<script setup>
import { ref, computed, onMounted, onUnmounted } from "vue"
2 months ago
import { useWebAdmisionStore } from "../../store/web"
2 months ago
2 months ago
const store = useWebAdmisionStore()
2 months ago
const isMobile = ref(false)
2 months ago
const checkScreen = () => (isMobile.value = window.innerWidth < 768)
const now = ref(new Date())
let timer = null
2 months ago
onMounted(() => {
checkScreen()
window.addEventListener("resize", checkScreen)
2 months ago
if (!store.procesoPrincipal) store.cargarProcesos()
timer = setInterval(() => (now.value = new Date()), 60_000)
2 months ago
})
onUnmounted(() => {
window.removeEventListener("resize", checkScreen)
2 months ago
if (timer) clearInterval(timer)
2 months ago
})
2 months ago
const toDate = (value) => {
if (!value) return null
const d = new Date(value)
return isNaN(d.getTime()) ? null : d
}
2 months ago
const startOfDay = (d) => {
const x = new Date(d)
x.setHours(0, 0, 0, 0)
return x
}
2 months ago
const endOfDay = (d) => {
const x = new Date(d)
x.setHours(23, 59, 59, 999)
return x
}
2 months ago
const inRange = (n, start, end) => {
if (!start || !end) return false
return n >= startOfDay(start) && n <= endOfDay(end)
}
2 months ago
const sameDay = (a, b) =>
a.getFullYear() === b.getFullYear() &&
a.getMonth() === b.getMonth() &&
a.getDate() === b.getDate()
2 months ago
const fmtShort = (value) => {
const d = toDate(value)
if (!d) return "Por definir"
return d.toLocaleDateString("es-PE", { day: "2-digit", month: "short" })
}
const fmtLong = (value) => {
const d = toDate(value)
if (!d) return "Por definir"
return d.toLocaleDateString("es-PE", {
day: "2-digit",
month: "short",
year: "numeric",
})
}
const fmtRange = (start, end) => {
const s = toDate(start)
const e = toDate(end)
if (s && e) return `${fmtShort(s)} ${fmtShort(e)}`
if (s && !e) return fmtLong(s)
if (!s && e) return fmtLong(e)
return "Por definir"
}
const tituloProceso = computed(() => {
const p = store.procesoPrincipal
if (!p) return "Proceso de Admisión"
return p.titulo || p.tipo_proceso || "Proceso de Admisión"
})
2 months ago
2 months ago
const active = computed(() => {
const p = store.procesoPrincipal
if (!p) return { pre: false, ins: false, exa: false, res: false, bio: false }
const n = now.value
const preIni = toDate(p.fecha_inicio_preinscripcion)
const preFin = toDate(p.fecha_fin_inscripcion) || toDate(p.fecha_fin_preinscripcion)
const insIni = toDate(p.fecha_inicio_inscripcion)
const insFin = toDate(p.fecha_fin_inscripcion)
const exa = toDate(p.fecha_examen1)
const res = toDate(p.fecha_resultados)
const bioIni = toDate(p.fecha_inicio_biometrico)
const bioFin = toDate(p.fecha_fin_biometrico)
2 months ago
return {
pre: inRange(n, preIni, preFin),
ins: inRange(n, insIni, insFin),
exa: exa ? sameDay(n, exa) : false,
res: res ? sameDay(n, res) : false,
bio: inRange(n, bioIni, bioFin),
}
2 months ago
})
const noActive = computed(() => {
const a = active.value
return !a.pre && !a.ins && !a.exa && !a.res && !a.bio
})
const getStepStatus = (index, p) => {
const n = now.value
const preIni = toDate(p.fecha_inicio_preinscripcion)
const preFin = toDate(p.fecha_fin_inscripcion) || toDate(p.fecha_fin_preinscripcion)
const insIni = toDate(p.fecha_inicio_inscripcion)
const insFin = toDate(p.fecha_fin_inscripcion)
const exa = toDate(p.fecha_examen1)
const res = toDate(p.fecha_resultados)
2 months ago
const bioIni = toDate(p.fecha_inicio_biometrico)
const bioFin = toDate(p.fecha_fin_biometrico)
const activePre = inRange(n, preIni, preFin)
const activeIns = inRange(n, insIni, insFin)
const activeExa = exa ? sameDay(n, exa) : false
const activeRes = res ? sameDay(n, res) : false
const activeBio = inRange(n, bioIni, bioFin)
const passed = (end) => (end ? n > endOfDay(end) : false)
const hasDates = [
Boolean(preIni || preFin),
Boolean(insIni || insFin),
Boolean(exa),
Boolean(res),
Boolean(bioIni || bioFin),
][index]
if (!hasDates) return "wait"
if (index === 0) return activePre ? "process" : passed(preFin) ? "finish" : "wait"
if (index === 1) return activeIns ? "process" : passed(insFin) ? "finish" : "wait"
if (index === 2) return activeExa ? "process" : passed(exa) ? "finish" : "wait"
if (index === 3) return activeRes ? "process" : passed(res) ? "finish" : "wait"
if (index === 4) return activeBio ? "process" : passed(bioFin) ? "finish" : "wait"
return "wait"
}
2 months ago
2 months ago
const withActiveBadge = (label, isActive) => (isActive ? `🟢 ${label}` : label)
const stepsItems = computed(() => {
const p = store.procesoPrincipal || {}
const a = active.value
return [
{
title: "Preinscripción Virtual",
description: withActiveBadge(
fmtRange(p.fecha_inicio_preinscripcion, p.fecha_fin_inscripcion),
a.pre
),
status: getStepStatus(0, p),
},
{
title: "Inscripción Presencial (Lugar establecido por las sedes)",
2 months ago
description: withActiveBadge(
fmtRange(p.fecha_inicio_inscripcion, p.fecha_fin_inscripcion),
a.ins
),
status: getStepStatus(1, p),
},
{
title: "Examen",
description: withActiveBadge(fmtLong(p.fecha_examen1), a.exa),
status: getStepStatus(2, p),
},
{
title: "Resultados",
description: withActiveBadge(fmtLong(p.fecha_resultados), a.res),
status: getStepStatus(3, p),
},
{
title: "Control Biométrico (Ingresantes)",
description: withActiveBadge(
fmtRange(p.fecha_inicio_biometrico, p.fecha_fin_biometrico),
a.bio
),
status: getStepStatus(4, p),
},
]
})
const tareasHoy = computed(() => {
const p = store.procesoPrincipal
if (!p) return []
const a = active.value
const tareas = []
if (a.pre) {
2 months ago
tareas.push("Entra a la Preinscripción Virtual y completa tus datos sin apuro (verifica nombres y DNI).")
tareas.push("Al terminar, descarga e imprime tu solicitud de preinscripción y revisa los Requisitos del proceso.")
}
2 months ago
if (a.ins) {
2 months ago
tareas.push("Acércate al Campus Universitario para la Inscripción Presencial.")
tareas.push("Lleva tu DNI y los documentos/pagos solicitados.")
}
2 months ago
if (a.exa) {
2 months ago
tareas.push("Hoy es el Examen: llega temprano, lleva tu DNI, constancia de inscripción y sigue las indicaciones del reglamento.")
2 months ago
}
2 months ago
if (a.res) {
2 months ago
tareas.push("Hoy salen Resultados.")
2 months ago
}
2 months ago
if (a.bio) {
2 months ago
tareas.push("Si ingresaste: acércate al control biométrico dentro de las fechas indicadas.")
2 months ago
}
2 months ago
if (tareas.length === 0) {
2 months ago
tareas.push("Revisa las fechas del proceso en los pasos de arriba.")
tareas.push("Si aún no inicia: alístate con requisitos, documentos y pagos para no correr a última hora.")
2 months ago
}
2 months ago
return tareas
})
2 months ago
</script>
2 months ago
<style scoped>
.process-section {
2 months ago
padding: 30px 0;
background: #ffffff;
2 months ago
font-family: "Times New Roman", Times, serif;
2 months ago
}
.section-container {
max-width: 1100px;
margin: 0 auto;
padding: 0 20px;
}
.section-header {
text-align: center;
2 months ago
margin-bottom: 18px;
2 months ago
}
.section-title {
font-size: 2.1rem;
2 months ago
font-weight: 700;
2 months ago
color: #2c3e50;
2 months ago
margin: 0 0 6px 0;
2 months ago
line-height: 1.2;
}
.section-subtitle {
font-size: 1rem;
color: #777;
margin: 0;
line-height: 1.4;
}
2 months ago
.process-card {
border: 1px solid #e5e7eb;
border-radius: 14px;
padding: 16px 14px 12px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.04);
background: #fff;
2 months ago
}
.modern-steps {
2 months ago
padding: 8px 8px;
}
.modern-steps :deep(.ant-steps-item-title) {
white-space: normal !important;
overflow: visible !important;
text-overflow: clip !important;
2 months ago
max-width: none !important;
}
.modern-steps :deep(.ant-steps-item-content) {
min-width: 0;
2 months ago
width: 100%;
}
.modern-steps :deep(.ant-steps-item-container) {
align-items: flex-start;
2 months ago
}
.modern-steps :deep(.ant-steps-item) {
flex: 1 1 0;
2 months ago
}
.modern-steps :deep(.ant-steps-item-title) {
2 months ago
font-size: 0.95rem;
font-weight: 700;
2 months ago
color: #34495e;
2 months ago
line-height: 1.15;
2 months ago
}
.modern-steps :deep(.ant-steps-item-description) {
2 months ago
font-size: 0.82rem;
2 months ago
color: #888;
margin-top: 2px;
2 months ago
line-height: 1.25;
2 months ago
}
.modern-steps :deep(.ant-steps-item-icon) {
2 months ago
width: 30px;
height: 30px;
line-height: 30px;
2 months ago
font-size: 13px;
}
.modern-steps :deep(.ant-steps-item-tail::after) {
height: 2px;
background: #dfe6e9;
}
.modern-steps :deep(.ant-steps-item-process .ant-steps-item-icon) {
2 months ago
background-color: #1e3a8a;
border-color: #1e3a8a;
2 months ago
}
2 months ago
.modern-steps :deep(.ant-steps-item-finish .ant-steps-item-icon > .ant-steps-icon) {
color: #1e3a8a;
}
.modern-steps :deep(.ant-steps-item-finish .ant-steps-item-icon) {
border-color: #1e3a8a;
2 months ago
}
2 months ago
.process-note {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
font-size: 0.86rem;
color: #6b7280;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #1e3a8a;
flex-shrink: 0;
}
2 months ago
2 months ago
.help-box {
margin-top: 14px;
border-top: 1px dashed #e5e7eb;
padding-top: 12px;
}
.help-title {
font-weight: 700;
color: #1e3a8a;
margin-bottom: 10px;
}
.help-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.help-item {
border: 1px solid #eef2ff;
background: #fbfcff;
border-radius: 12px;
padding: 12px;
}
.help-label {
font-weight: 700;
color: #2c3e50;
margin-bottom: 8px;
}
.tiny-hint {
margin-top: 10px;
font-size: 0.86rem;
color: #6b7280;
}
.badges {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
2 months ago
2 months ago
.badge {
font-size: 0.82rem;
padding: 6px 10px;
border-radius: 999px;
font-weight: 700;
}
.badge-blue {
background: rgba(30, 58, 138, 0.08);
color: #1e3a8a;
border: 1px solid rgba(30, 58, 138, 0.18);
}
.badge-green {
background: rgba(16, 185, 129, 0.08);
color: #047857;
border: 1px solid rgba(16, 185, 129, 0.18);
}
.badge-orange {
background: rgba(245, 158, 11, 0.10);
color: #92400e;
border: 1px solid rgba(245, 158, 11, 0.22);
}
.badge-gray {
background: rgba(107, 114, 128, 0.08);
color: #374151;
border: 1px solid rgba(107, 114, 128, 0.18);
}
.help-list {
margin: 0;
padding-left: 18px;
color: #4b5563;
line-height: 1.55;
}
.help-actions {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
/* Nota campus */
.campus-note {
margin-top: 12px;
padding: 10px 12px;
border-radius: 12px;
background: #f8fafc;
border: 1px solid #e5e7eb;
color: #374151;
line-height: 1.5;
}
2 months ago
@media (max-width: 992px) {
.section-title {
font-size: 1.85rem;
}
.modern-steps :deep(.ant-steps-item-title) {
font-size: 0.92rem;
}
}
@media (max-width: 768px) {
.process-section {
padding: 24px 0;
}
.section-title {
font-size: 1.55rem;
}
.process-card {
padding: 12px 10px 10px;
}
.modern-steps {
padding: 4px 4px;
}
.modern-steps :deep(.ant-steps-item-icon) {
width: 28px;
height: 28px;
line-height: 28px;
}
2 months ago
.help-grid {
grid-template-columns: 1fr;
}
2 months ago
}
</style>