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.

570 lines
13 KiB
Vue

2 months ago
<!-- DashboardPostulante.vue (test + procesos activos) -->
2 months ago
<template>
<a-spin :spinning="loading">
2 months ago
<div class="section-container">
<div class="page">
<!-- Topbar -->
<div class="topbar">
<div class="topbarLeft">
<div class="hello">Bienvenido, {{ authStore.userName }}</div>
<div class="sub">DNI: {{ authStore.userDni || "No registrado" }}</div>
2 months ago
</div>
</div>
2 months ago
<a-divider class="softDivider" />
<!-- TEST -->
<a-card :bordered="false" class="testBox">
<div class="testHead">
<div class="testHeadLeft">
<div class="testTitle">Tu test diagnóstico</div>
<div class="testSub">
Es un <b>test referencial</b> para practicar y medir tu nivel. <b>No afecta</b> tu postulación oficial.
</div>
<div class="mt12">
<a-space direction="vertical" size="small">
<div class="bulletRow">
<span class="dot" />
<span>10 preguntas 10 min aprox. resultado inmediato</span>
</div>
<div class="bulletRow">
<span class="dot" />
<span>
Si tu proceso pide secuencia, también puedes usar la del pago de tu
<b>Carpeta de Postulante</b> (no pagas extra por este test).
</span>
</div>
</a-space>
</div>
2 months ago
</div>
2 months ago
<div class="testHeadRight">
<a-button type="primary" size="large" class="btnTest" :disabled="!canGoTest" @click="goToTest" block>
Ir al test
</a-button>
2 months ago
2 months ago
<div class="microHelp" v-if="!canGoTest">Aún no tienes un test asignado.</div>
2 months ago
</div>
</div>
2 months ago
</a-card>
2 months ago
2 months ago
<a-divider class="softDivider" />
2 months ago
2 months ago
<!-- PROCESOS ACTIVOS -->
<div class="sectionHead">
<div>
<div class="sectionTitle">Procesos activos</div>
<div class="sectionSub">Revisa los procesos habilitados y postula cuando corresponda.</div>
2 months ago
</div>
2 months ago
<div class="sectionRight">
<a-badge count="Nuevo" class="new-badge" />
2 months ago
</div>
</div>
2 months ago
<div class="tableWrap mt12">
<a-table
:columns="processColumns"
:data-source="state.processes"
row-key="id"
:loading="loading"
:pagination="{ pageSize: 6 }"
:scroll="{ x: 720 }"
class="modernTable"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<a-tag class="status-tag" :color="record.statusColor">{{ record.statusText }}</a-tag>
</template>
<template v-else-if="column.key === 'eligibility'">
<a-badge
:status="record.isEligible ? 'success' : 'error'"
:text="record.isEligible ? 'Apto' : 'No apto'"
/>
</template>
<template v-else-if="column.key === 'actions'">
<div class="actions">
<a-button size="small" block class="actionBtn" @click="onViewProcess(record)">Ver</a-button>
<a-button
size="small"
type="primary"
block
class="actionBtnPrimary"
:disabled="!record.canApply"
@click="onApply(record)"
>
Postular
</a-button>
</div>
<div v-if="record.canApply === false && record.blockReason" class="microHelp">
{{ record.blockReason }}
</div>
</template>
</template>
</a-table>
2 months ago
</div>
</div>
2 months ago
<!-- Mensaje cuando no hay procesos -->
<a-empty
v-if="!loading && state.processes.length === 0"
class="mt12"
description="No hay procesos activos por el momento."
/>
2 months ago
</div>
</a-spin>
</template>
2 months ago
<script setup>
2 months ago
import { computed, onMounted, reactive, ref } from "vue";
import { Modal, message } from "ant-design-vue";
import { useRouter } from "vue-router";
import { useAuthStore } from "../../store/postulanteStore";
2 months ago
2 months ago
// ✅ Ajusta a tus rutas reales
const ROUTE_TEST_PANEL = { name: "PanelTest" }; // tu panel del test
const ROUTE_PROCESS_DETAIL = (id) => ({ name: "ProcesoDetalle", params: { id } }); // opcional
2 months ago
2 months ago
const router = useRouter();
const authStore = useAuthStore();
2 months ago
2 months ago
const loading = ref(false);
2 months ago
2 months ago
const state = reactive({
test: { hasAssigned: true }, // viene de tu backend/store
processes: [], // procesos activos
2 months ago
});
2 months ago
const canGoTest = computed(() => !!state.test.hasAssigned);
2 months ago
const processColumns = computed(() => [
{ title: "Proceso", dataIndex: "name", key: "name", ellipsis: true },
2 months ago
{ title: "Estado", key: "status", responsive: ["sm"] },
{ title: "Aptitud", key: "eligibility", responsive: ["md"] },
2 months ago
{ title: "Acciones", key: "actions" },
]);
2 months ago
// ✅ Mock: reemplaza por tu store / api real
2 months ago
const api = {
async getDashboard() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
2 months ago
test: { hasAssigned: true },
processes: [
{
id: 10,
name: "Admisión 2026-I",
statusText: "ABIERTO",
statusColor: "blue",
isEligible: true,
canApply: true,
blockReason: "",
},
{
id: 11,
name: "Admisión Extraordinaria 2026",
statusText: "PRONTO",
statusColor: "gold",
isEligible: false,
canApply: false,
blockReason: "Este proceso aún no permite postular.",
},
2 months ago
],
});
2 months ago
}, 250);
2 months ago
});
},
2 months ago
async applyToProcess(processId) {
2 months ago
return new Promise((resolve) => setTimeout(resolve, 300));
},
};
async function fetchDashboard() {
loading.value = true;
2 months ago
try {
2 months ago
const data = await api.getDashboard();
2 months ago
state.test = { ...state.test, ...data.test };
state.processes = Array.isArray(data.processes) ? data.processes : [];
2 months ago
} catch {
message.error("No se pudo cargar el dashboard.");
2 months ago
} finally {
2 months ago
loading.value = false;
2 months ago
}
}
2 months ago
function goToTest() {
if (!canGoTest.value) return;
2 months ago
Modal.confirm({
2 months ago
title: "Test diagnóstico",
content: "Este test es referencial y no afecta tu postulación. ¿Deseas continuar?",
2 months ago
okText: "Continuar",
2 months ago
cancelText: "Cancelar",
2 months ago
onOk() {
router.push("/portal-postulante/test");
2 months ago
},
});
2 months ago
}
2 months ago
function onViewProcess(process) {
// router.push(ROUTE_PROCESS_DETAIL(process.id));
message.info(`Abrir detalle del proceso: ${process.name}`);
}
function onApply(process) {
2 months ago
if (!process.canApply) return;
2 months ago
2 months ago
Modal.confirm({
title: "Confirmar postulación",
content: `¿Deseas postular al proceso "${process.name}"?`,
okText: "Postular",
cancelText: "Cancelar",
async onOk() {
try {
await api.applyToProcess(process.id);
message.success("Postulación registrada.");
await fetchDashboard();
} catch {
message.error("No se pudo completar la postulación.");
}
},
});
2 months ago
}
2 months ago
onMounted(fetchDashboard);
2 months ago
</script>
2 months ago
<style scoped>
2 months ago
/* =========================
BASE estilo Convocatorias
========================= */
.dashboard-modern {
position: relative;
padding: 40px 0;
font-family: "Times New Roman", Times, serif;
background: #fbfcff;
overflow: hidden;
}
.dashboard-modern::before {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
z-index: 0;
background-image:
repeating-linear-gradient(
to right,
rgba(13, 27, 82, 0.06) 0,
rgba(13, 27, 82, 0.06) 1px,
transparent 1px,
transparent 24px
),
repeating-linear-gradient(
to bottom,
rgba(13, 27, 82, 0.06) 0,
rgba(13, 27, 82, 0.06) 1px,
transparent 1px,
transparent 24px
);
opacity: 0.55;
}
.section-container {
position: relative;
z-index: 1;
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
2 months ago
2 months ago
/* Layout container */
2 months ago
.page {
width: 100%;
max-width: 1120px;
margin: 0 auto;
2 months ago
padding: 0;
2 months ago
}
2 months ago
2 months ago
/* Helpers */
.mt12 { margin-top: 12px; }
.microHelp { font-size: 0.95rem; color: #666; margin-top: 6px; }
.softDivider { margin: 18px 0; }
/* =========================
Topbar
========================= */
2 months ago
.topbar {
2 months ago
display: flex;
justify-content: space-between;
2 months ago
gap: 14px;
2 months ago
flex-wrap: wrap;
2 months ago
align-items: flex-start;
2 months ago
}
2 months ago
.hello {
2 months ago
font-size: 2rem;
font-weight: 700;
color: #0d1b52;
margin: 0;
2 months ago
}
2 months ago
2 months ago
.sub {
2 months ago
margin-top: 6px;
font-size: 1rem;
color: #666;
2 months ago
}
2 months ago
.topbarRight {
min-width: 260px;
display: flex;
2 months ago
justify-content: flex-end;
2 months ago
}
2 months ago
2 months ago
.btnTop {
min-width: 180px;
2 months ago
height: 46px;
border-radius: 10px;
font-weight: 700;
2 months ago
}
2 months ago
2 months ago
/* =========================
Test destacado (card principal)
========================= */
2 months ago
.testBox {
2 months ago
position: relative;
border: none;
border-radius: 16px;
box-shadow: 0 10px 34px rgba(0, 0, 0, 0.08);
background: #fff;
}
.testBox :deep(.ant-card-body) {
padding: 28px;
}
/* Badge tipo convocatorias */
.cardBadge {
position: absolute;
top: -12px;
left: 24px;
background: linear-gradient(45deg, #1890ff, #52c41a);
color: #fff;
padding: 6px 16px;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 700;
2 months ago
}
2 months ago
.testHead {
2 months ago
display: flex;
2 months ago
justify-content: space-between;
2 months ago
gap: 16px;
flex-wrap: wrap;
2 months ago
align-items: flex-start;
2 months ago
}
2 months ago
.testTitle {
2 months ago
font-size: 1.55rem;
font-weight: 700;
color: #1a237e;
2 months ago
}
2 months ago
2 months ago
.testSub {
2 months ago
margin-top: 8px;
font-size: 1rem;
color: #666;
line-height: 1.55;
max-width: 760px;
2 months ago
}
2 months ago
.testHeadRight {
min-width: 260px;
2 months ago
display: flex;
2 months ago
flex-direction: column;
2 months ago
gap: 8px;
2 months ago
align-items: flex-end;
2 months ago
}
2 months ago
.btnTest {
2 months ago
height: 52px;
border-radius: 12px;
font-weight: 700;
background: linear-gradient(135deg, #1a237e 0%, #283593 100%);
border: none;
2 months ago
}
2 months ago
2 months ago
/* bullets */
.bulletRow {
display: flex;
2 months ago
gap: 10px;
2 months ago
align-items: flex-start;
font-size: 1rem;
color: #666;
line-height: 1.55;
2 months ago
}
2 months ago
2 months ago
.dot {
width: 8px;
height: 8px;
2 months ago
border-radius: 999px;
2 months ago
background: #1a237e;
margin-top: 9px;
flex: 0 0 auto;
2 months ago
}
2 months ago
2 months ago
/* =========================
Section header
========================= */
2 months ago
.sectionHead {
2 months ago
display: flex;
justify-content: space-between;
gap: 12px;
2 months ago
flex-wrap: wrap;
align-items: flex-start;
2 months ago
}
2 months ago
.sectionRight { margin-top: 4px; }
2 months ago
.sectionTitle {
2 months ago
font-size: 1.55rem;
font-weight: 700;
color: #0d1b52;
margin: 0;
2 months ago
}
2 months ago
2 months ago
.sectionSub {
2 months ago
margin-top: 8px;
font-size: 1rem;
color: #666;
line-height: 1.55;
2 months ago
}
2 months ago
2 months ago
/* Badge Nuevo (como convocatorias) */
.new-badge :deep(.ant-badge-count) {
background: linear-gradient(45deg, #ff6b6b, #ffd700);
box-shadow: 0 2px 8px rgba(255, 107, 107, 0.3);
border-radius: 999px;
}
/* =========================
Table "card look"
========================= */
2 months ago
.tableWrap {
width: 100%;
overflow-x: auto;
2 months ago
border-radius: 16px;
box-shadow: 0 10px 34px rgba(0, 0, 0, 0.08);
background: #fff;
padding: 8px;
2 months ago
}
2 months ago
:deep(.modernTable .ant-table) {
background: transparent;
}
:deep(.modernTable .ant-table-container) {
2 months ago
overflow-x: auto;
2 months ago
border-radius: 12px;
}
:deep(.modernTable .ant-table-thead > tr > th) {
background: rgba(13, 27, 82, 0.03);
color: #0d1b52;
font-weight: 700;
}
:deep(.modernTable .ant-table-tbody > tr > td) {
color: #666;
2 months ago
}
2 months ago
/* Status pill estilo convocatorias */
.status-tag {
font-weight: 700;
padding: 4px 12px;
border-radius: 999px;
white-space: nowrap;
}
/* Actions */
2 months ago
.actions {
display: grid;
grid-template-columns: 1fr 1fr;
2 months ago
gap: 8px;
2 months ago
}
2 months ago
2 months ago
.actionBtn {
border-radius: 10px;
height: 38px;
2 months ago
font-weight: 700;
}
2 months ago
2 months ago
.actionBtnPrimary {
border-radius: 10px;
height: 38px;
font-weight: 700;
}
2 months ago
2 months ago
/* Responsive */
2 months ago
@media (max-width: 768px) {
2 months ago
.hide-mobile{
display: none !important;
2 months ago
}
2 months ago
/* ✅ Quita el padding superior del wrapper (era 40px) */
.test-modern{
padding-top: 0 !important;
padding-bottom: 24px; /* opcional */
2 months ago
}
2 months ago
/* ✅ Quita padding lateral del container para que sea edge-to-edge */
.section-container{
max-width: none;
padding: 0 !important;
margin: 0 !important;
2 months ago
}
2 months ago
/* ✅ Card principal a todo el ancho y pegado arriba */
.hero{
grid-template-columns: 1fr;
width: 100%;
margin: 0 !important;
border-radius: 0 !important;
/* importante: sin “espacio arriba” */
padding: 14px 16px 18px !important;
2 months ago
}
2 months ago
/* ✅ Asegura que el primer texto no empuje hacia abajo */
.heroKicker{ margin-top: 0 !important; }
.heroTitle{ margin-top: 6px !important; }
.heroText{ margin-top: 8px !important; }
/* Para que la sección “Tu camino” no se pegue a los bordes */
.section{
padding: 0 16px;
2 months ago
}
2 months ago
/* Facts */
.heroFacts{
grid-template-columns: repeat(2, minmax(0, 1fr));
2 months ago
}
}
2 months ago
@media (max-width: 576px){
.heroFacts{
grid-template-columns: 1fr;
}
2 months ago
}
2 months ago
</style>