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

<!-- 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>