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.
314 lines
7.2 KiB
Vue
314 lines
7.2 KiB
Vue
<!-- src/components/web/CarrerasSection.vue -->
|
|
<template>
|
|
<section class="carreras-section">
|
|
<div class="container">
|
|
<div class="header">
|
|
<div class="header-left">
|
|
<a-typography-title :level="2" class="title">
|
|
Carreras del Área: {{ areaNombre }}
|
|
</a-typography-title>
|
|
|
|
<a-typography-paragraph class="subtitle">
|
|
Información del programa, grado/título y resumen
|
|
</a-typography-paragraph>
|
|
</div>
|
|
</div>
|
|
|
|
<a-divider class="divider" />
|
|
|
|
<div v-if="filteredCarreras.length">
|
|
<a-row :gutter="[18, 18]">
|
|
<a-col
|
|
v-for="carrera in filteredCarreras"
|
|
:key="carrera.id"
|
|
:xs="24"
|
|
:sm="12"
|
|
:lg="8"
|
|
class="career-col"
|
|
>
|
|
<!-- CINTA ARRIBA: ÁREA -->
|
|
<a-badge-ribbon :text="areaNombre" color="blue">
|
|
<a-card
|
|
hoverable
|
|
class="card"
|
|
:bodyStyle="{ padding: '12px' }"
|
|
@click="goToDetail(carrera)"
|
|
>
|
|
<!-- ✅ SOLO 1 IMAGEN: COVER -->
|
|
<template v-if="getImage(carrera)" #cover>
|
|
<div
|
|
class="cover"
|
|
:style="{ backgroundImage: `url(${getImage(carrera)})` }"
|
|
>
|
|
<div class="cover-overlay" />
|
|
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Contenido -->
|
|
<div class="content">
|
|
<a-typography-title
|
|
:level="5"
|
|
class="card-title"
|
|
:content="carrera.escuela_profesional"
|
|
:ellipsis="{ rows: 2, tooltip: carrera.escuela_profesional }"
|
|
/>
|
|
|
|
|
|
|
|
<!-- ✅ Resumen: 20 palabras + 4 filas -->
|
|
<a-typography-paragraph
|
|
class="desc"
|
|
:title="carrera.resumen_general || 'Sin resumen registrado.'"
|
|
>
|
|
{{ truncateWords(carrera.resumen_general || "Sin resumen registrado.", 20) }}
|
|
</a-typography-paragraph>
|
|
|
|
<div class="actions">
|
|
<a-button type="link" class="read-more" @click.stop="goToDetail(carrera)">
|
|
Ver detalle
|
|
<ArrowRightOutlined />
|
|
</a-button>
|
|
|
|
<a-tag color="gold" class="tag-soft">
|
|
{{ (carrera.perfil_de_egresado?.competencias_especificas?.length || 0) }} CE
|
|
</a-tag>
|
|
</div>
|
|
</div>
|
|
</a-card>
|
|
</a-badge-ribbon>
|
|
</a-col>
|
|
</a-row>
|
|
</div>
|
|
|
|
<a-empty
|
|
v-else
|
|
:description="`No hay carreras registradas para el área: ${areaNombre}.`"
|
|
/>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed } from "vue"
|
|
import { ArrowRightOutlined } from "@ant-design/icons-vue"
|
|
import { useRouter } from "vue-router"
|
|
|
|
import { carreras } from "../../../../data/carreras"
|
|
import { areas } from "../../../../data/areas"
|
|
|
|
const props = defineProps({
|
|
areaId: { type: Number, default: 1 },
|
|
})
|
|
|
|
const router = useRouter()
|
|
|
|
const areaNombre = computed(() => {
|
|
const a = (areas ?? []).find((x) => Number(x.id) === Number(props.areaId))
|
|
return a?.nombre || `Área ${props.areaId}`
|
|
})
|
|
|
|
const filteredCarreras = computed(() => {
|
|
return (carreras ?? []).filter((c) => Number(c.areaId) === Number(props.areaId))
|
|
})
|
|
|
|
// ✅ más robusto: ignora "/", "#", etc.
|
|
const getImage = (c) => {
|
|
const invalid = new Set(["/", "#", "null", "undefined"])
|
|
const candidates = [c?.imagen, c?.imagen1, c?.imagen2, c?.imagen3]
|
|
.map((x) => (x ?? "").toString().trim())
|
|
.filter(Boolean)
|
|
return candidates.find((src) => !invalid.has(src)) || ""
|
|
}
|
|
|
|
const goToDetail = (carrera) => {
|
|
router.push({ name: "ingenierias-detalle", params: { id: carrera.id } })
|
|
}
|
|
|
|
const truncateWords = (text, limit = 20) => {
|
|
if (!text) return ""
|
|
const words = String(text).trim().split(/\s+/).filter(Boolean)
|
|
if (words.length <= limit) return words.join(" ")
|
|
return words.slice(0, limit).join(" ") + "..."
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* ✅ AntDV “puro” + institucional: fondo limpio, bordes suaves */
|
|
.carreras-section {
|
|
padding: 64px 0;
|
|
background: #f5f7fa; /* parecido a layout antd */
|
|
}
|
|
|
|
/* ✅ Forzar Times en TODO (incluye AntDV interno) */
|
|
.carreras-section :deep(*) {
|
|
font-family: "Times New Roman", Times, serif !important;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 0 24px;
|
|
}
|
|
|
|
.header {
|
|
display: flex;
|
|
justify-content: center;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.header-left {
|
|
width: 100%;
|
|
max-width: 880px;
|
|
text-align: center;
|
|
}
|
|
|
|
.title {
|
|
margin: 0 !important;
|
|
font-weight: 900;
|
|
color: rgba(0, 0, 0, 0.88);
|
|
letter-spacing: -0.2px;
|
|
}
|
|
|
|
.subtitle {
|
|
margin: 8px 0 0 !important;
|
|
color: rgba(0, 0, 0, 0.60);
|
|
line-height: 1.6;
|
|
font-size: 0.98rem;
|
|
}
|
|
|
|
.divider {
|
|
margin: 16px 0 22px !important;
|
|
}
|
|
|
|
/* columnas parejitas */
|
|
.career-col {
|
|
display: flex;
|
|
}
|
|
|
|
/* ✅ Card más “institucional” (antd-like) */
|
|
.card {
|
|
width: 100%;
|
|
border-radius: 14px;
|
|
border: 1px solid #f0f0f0;
|
|
overflow: hidden;
|
|
background: #fff;
|
|
transition: box-shadow 0.18s ease, transform 0.18s ease;
|
|
}
|
|
|
|
.card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
/* ✅ Cover más pequeño y limpio */
|
|
.cover {
|
|
position: relative;
|
|
height: 140px;
|
|
background-size: cover; /* moderno */
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
background-color: #fafafa;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.cover-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
|
|
}
|
|
|
|
/* pill institucional */
|
|
.pill {
|
|
position: absolute;
|
|
left: 12px;
|
|
bottom: 12px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 7px 10px;
|
|
border-radius: 999px;
|
|
color: rgba(255, 255, 255, 0.95);
|
|
background: rgba(22, 119, 255, 0.45); /* azul antd */
|
|
border: 1px solid rgba(255, 255, 255, 0.22);
|
|
backdrop-filter: blur(6px);
|
|
font-size: 0.86rem;
|
|
font-weight: 700;
|
|
max-width: calc(100% - 24px);
|
|
}
|
|
|
|
.pill-strong { font-weight: 900; }
|
|
.pill-text {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* ✅ contenido con “actions” abajo siempre */
|
|
.content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
height: 100%;
|
|
}
|
|
|
|
/* título compacto */
|
|
.card-title {
|
|
margin: 0 !important;
|
|
font-size: 1.05rem;
|
|
font-weight: 900;
|
|
color: rgba(0, 0, 0, 0.88);
|
|
min-height: 44px; /* empareja cards */
|
|
}
|
|
|
|
.meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.tag-soft {
|
|
border-radius: 999px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.meta-text {
|
|
color: rgba(0, 0, 0, 0.70);
|
|
line-height: 1.35;
|
|
font-size: 0.92rem;
|
|
}
|
|
|
|
/* ✅ 20 palabras + 4 filas (clamp) */
|
|
.desc {
|
|
margin: 0 !important;
|
|
color: rgba(0, 0, 0, 0.65);
|
|
line-height: 1.55;
|
|
font-size: 0.94rem;
|
|
|
|
display: -webkit-box;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
|
|
min-height: calc(1.55em * 4);
|
|
}
|
|
|
|
/* acciones abajo */
|
|
.actions {
|
|
margin-top: auto;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.read-more {
|
|
padding: 0;
|
|
font-weight: 900;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.carreras-section {
|
|
padding: 52px 0;
|
|
}
|
|
}
|
|
</style>
|