Fix #11 - migrate-careers-db (#12)

Reviewed-on: #12
Co-authored-by: Luciano Giacchetta <giacchetta-@outlook.com>
Co-committed-by: Luciano Giacchetta <giacchetta-@outlook.com>
This commit is contained in:
Luciano Giacchetta 2025-08-20 19:23:09 -03:00 committed by Luciano Giacchetta
parent caa14b480e
commit a716982652
27 changed files with 317 additions and 1028 deletions

View File

@ -8,6 +8,9 @@ export default defineConfig({
compressHTML: true,
site: process.env.ASTRO_SITE,
trailingSlash: 'always',
image: {
domains: ['assets.codyops.com','assets.codyops.com.br','assets.codyops.com.es'],
},
integrations: [
markdownIntegration({
remarkPlugins: [],

View File

Before

Width:  |  Height:  |  Size: 255 KiB

After

Width:  |  Height:  |  Size: 255 KiB

View File

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 192 KiB

View File

@ -1,31 +0,0 @@
---
import { Image } from 'astro:assets';
type Props = {
as?: HTMLElement;
className?: string;
id?: string;
backgroundImage: any;
backgroundAlt: string;
};
const {
as = 'div',
className = '',
id = '',
backgroundImage,
backgroundAlt,
}: Props = Astro.props;
const Tag: HTMLElement = as;
---
<Tag class={`${className}`} id={id}>
<div class="banner py-10 position-relative">
<figure class="banner__background-container-image">
<div class="overlay"></div>
<Image src={backgroundImage} alt={backgroundAlt} />
</figure>
<slot />
</div>
</Tag>

View File

@ -46,11 +46,11 @@ const { course }: Props = Astro.props;
<h3>Incluido en Carreras:</h3>
</div>
<div class="d-flex gap-2 mt-3">
{course.codyops_careers.map((carrer: any) => (
<a class="btn btn-outline-success mb-1" href={`/carreras/${carrer.codyops_careers_id.slug}/`}>
{carrer.codyops_careers_id.name}
{course.codyops_careers.map((career: any) => (
<a class="btn btn-outline-success mb-1" href={`/carreras/${career.codyops_careers_id.slug}/`}>
{career.codyops_careers_id.name}
</a>
))}
))}
</div>
</div>
<style>
@ -70,4 +70,4 @@ const { course }: Props = Astro.props;
p {
color: white;
}
</style>
</style>

View File

@ -1,276 +0,0 @@
import { CourseLevel, PriceBox } from '../enums';
import type { Accordion, CareerPrice } from '../types';
export const awsDevopsCareerPrices: CareerPrice[] = [
{
description: 'Ejemplo 1: Al contado con un 60% de descuento.',
discount: true,
prices: [
{
title: 'Precio regular',
price: 'USD 300',
type: PriceBox.DiscountPrice,
nextSign: '-',
},
{
title: 'Dscto. 60%',
price: 'USD 180',
nextSign: '=',
},
{
title: 'Precio final',
price: 'USD 120',
type: PriceBox.FinalPrice,
},
],
},
{
description: 'Ejemplo 2: En tres cuotas mensuales con 40% de descuento.',
prices: [
{
title: 'Cuota 1 (mes 1)',
price: 'USD 60',
type: PriceBox.CuotePrice,
nextSign: '+',
},
{
title: 'Cuota 2 (mes 2)',
price: 'USD 60',
type: PriceBox.CuotePrice,
nextSign: '+',
},
{
title: 'Cuota 3 (mes 3)',
price: 'USD 60',
type: PriceBox.CuotePrice,
nextSign: '+',
},
{
title: 'Precio al finalizar el tercer mes',
price: 'USD 180',
type: PriceBox.FinalPrice,
},
],
},
];
export const awsDevopsCareerProgram: Accordion[] = [
{
title: 'Security, Identity & Compliance',
hours: '40 Horas',
children: [
{
title: 'Amazon Identity and Access Management (AWS IAM)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Certificate Manager',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Secrets Manager',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Key Management Service (AWS KMS)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Computing & Networking',
hours: '60 Horas',
children: [
{
title: 'Amazon Virtual Private Cloud ( AWS VPC)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Elastic Compute Cloud (AWS EC2)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Route 53',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon CloudFront',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Elastic Load Balancing',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon App Runner',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Serverless',
hours: '50 Horas',
children: [
{
title: 'Amazon Simple Notification Service (AWS SNS)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon API Gateway',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon EventBridge (Amazon CloudWatch Events)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Lambda',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Simple Queue Service (AWS SQS)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Infrastructure & Orchestration',
hours: '10 Horas',
children: [
{
title: 'Amazon CloudFormation',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Monitoring & Alerting',
hours: '20 Horas',
children: [
{
title: 'Amazon CloudWatch',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon CloudTrail ',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Continuous Integration',
hours: '50 Horas',
children: [
{
title: 'Amazon CodeArtifact',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon CodeBuild',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon CodeDeploy',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon CodePipeline',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon CodeStar',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Collaboration & Management',
hours: '10 Horas',
children: [
{
title: 'Amazon CodeCommit',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Containers Orchestration',
hours: '40 Horas',
children: [
{
title: 'Amazon Elastic Container Registry (AWS ECR)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Fargate',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Elastic Container Service (AWS ECS)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Elastic Kubernetes Service (AWS EKS)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Store',
hours: '50 Horas',
children: [
{
title: 'Amazon Simple Storage Service (AWS S3)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Elastic Block Store (AWS EBS)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Elastic File System (AWS EFS)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Relational Database Service (AWS RDS)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon DynamoDB',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
];

View File

@ -1,309 +0,0 @@
// eslint-disable-next-line import/extensions
import { CourseLevel, PriceBox } from '../enums';
import type { Accordion, CareerPrice } from '../types';
export const cloudopsCareerPrices: CareerPrice[] = [
{
description: 'Ejemplo 1: Al contado con un 60% de descuento.',
discount: true,
prices: [
{
title: 'Precio regular',
price: 'USD 300',
type: PriceBox.DiscountPrice,
nextSign: '-',
},
{
title: 'Dscto. 60%',
price: 'USD 180',
nextSign: '=',
},
{
title: 'Precio final',
price: 'USD 120',
type: PriceBox.FinalPrice,
},
],
},
{
description: 'Ejemplo 2: En tres cuotas mensuales con 40% de descuento.',
prices: [
{
title: 'Cuota 1 (mes 1)',
price: 'USD 60',
type: PriceBox.CuotePrice,
nextSign: '+',
},
{
title: 'Cuota 2 (mes 2)',
price: 'USD 60',
type: PriceBox.CuotePrice,
nextSign: '+',
},
{
title: 'Cuota 3 (mes 3)',
price: 'USD 60',
type: PriceBox.CuotePrice,
nextSign: '=',
},
{
title: 'Precio al finalizar el tercer mes',
price: 'USD 180',
type: PriceBox.FinalPrice,
},
],
},
];
export const cloudopsCareerProgram: Accordion[] = [
{
title: 'Collaboration and Productivity',
hours: '20 Horas',
children: [
{
title: 'Google Workspace I (Gmail - Classroom - Calendar - Drive)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Google Workspace II (Chat & Space - Meet - Docs - Sheets)',
courseLevel: CourseLevel.Intermediate,
hours: '10 Horas',
},
],
},
{
title: 'Development & Scripting',
hours: '18 Horas',
children: [
{
title: 'Bourne Again SHell (Bash)',
courseLevel: CourseLevel.Introduction,
hours: '8 Horas',
},
{
title: 'Bourne Again SHell (Bash)',
courseLevel: CourseLevel.Intermediate,
hours: '8 Horas',
},
{
title: 'Google Cloud Shell',
courseLevel: CourseLevel.Introduction,
hours: '2 Horas',
},
],
},
{
title: 'Infrastructure Orchestration',
hours: '54 Horas',
children: [
{
title: 'Terraform',
courseLevel: CourseLevel.Introduction,
hours: '20 Horas',
},
{
title: 'Terragrunt ',
courseLevel: CourseLevel.Intermediate,
hours: '20 Horas',
},
{
title: 'Pulumi ',
courseLevel: CourseLevel.Introduction,
hours: '14 Horas',
},
],
},
{
title: 'Software Control Managment',
hours: '14 Horas',
children: [
{
title: 'Git & GitHub',
courseLevel: CourseLevel.Introduction,
hours: '14 Horas',
},
],
},
{
title: 'Containers Orchestration',
hours: '60 Horas',
children: [
{
title: 'Docker',
courseLevel: CourseLevel.Introduction,
hours: '20 Horas',
},
{
title: 'Kubernetes',
courseLevel: CourseLevel.Introduction,
hours: '20 Horas',
},
{
title: 'Amazon Elastic Container Registry (ECR)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Elastic Kubernetes Service (EKS)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Continuous Integration',
hours: '14 Horas',
children: [
{
title: 'GitHub Actions',
courseLevel: CourseLevel.Introduction,
hours: '14 Horas',
},
],
},
{
title: 'Serverless',
hours: '10 Horas',
children: [
{
title: 'Amazon Simple Notification Service (SNS)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Deployment Automation',
hours: '10 Horas',
children: [
{
title: 'Helm Charts',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Public Cloud Services',
hours: '20 Horas',
children: [
{
title: 'Amazon Web Services',
courseLevel: CourseLevel.Introduction,
hours: '20 Horas',
},
],
},
{
title: 'Compute',
hours: '10 Horas',
children: [
{
title: 'Amazon Elastic Compute Cloud (EC2)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Administration & Government',
hours: '30 Horas',
children: [
{
title: 'Amazon CloudWatch',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'AWS CloudFormation',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'AWS Systems Manager',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Security, Identity and Compliance',
hours: '40 Horas',
children: [
{
title: 'AWS Certificate Manager',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'AWS Identity and Access Management (IAM) ',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'AWS Key Management Service (KMS)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'AWS Secrets Manager ',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Networking & Content Delivery',
hours: '40 Horas',
children: [
{
title: 'Amazon Virtual Private Cloud (VPC)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'AWS Route53',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'AWS Elastic Load Balancing',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon CloudFront',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Storage',
hours: '20 Horas',
children: [
{
title: 'Amazon Simple Storage Service (S3)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Amazon Elastic File System (EFS)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Database',
hours: '10 Horas',
children: [
{
title: 'Amazon Relational Database Service (RDS)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
];

View File

@ -1,237 +0,0 @@
import { CourseLevel, PriceBox } from '../enums';
import type { Accordion, CareerPrice } from '../types';
export const devopsCareerPrices: CareerPrice[] = [
{
description: 'Ejemplo 1: Al contado con un 60% de descuento.',
discount: true,
prices: [
{
title: 'Precio regular',
price: 'USD 180',
type: PriceBox.DiscountPrice,
nextSign: '-',
},
{
title: 'Dscto. 60%',
price: 'USD 108',
nextSign: '=',
},
{
title: 'Precio final',
price: 'USD 72',
type: PriceBox.FinalPrice,
},
],
},
{
description: 'Ejemplo 2: En tres cuotas mensuales con 40% de descuento.',
prices: [
{
title: 'Cuota 1 (mes 1)',
price: 'USD 36',
type: PriceBox.CuotePrice,
nextSign: '+',
},
{
title: 'Cuota 2 (mes 2)',
price: 'USD 36',
type: PriceBox.CuotePrice,
nextSign: '+',
},
{
title: 'Cuota 3 (mes 3)',
price: 'USD 36',
type: PriceBox.CuotePrice,
nextSign: '=',
},
{
title: 'Precio al finalizar el tercer mes',
price: 'USD 108',
type: PriceBox.FinalPrice,
},
],
},
];
export const devopsCareerProgram: Accordion[] = [
{
title: 'Collaboration and Productivity',
hours: '20 Horas',
children: [
{
title: 'Google Workspace I (Gmail - Classroom - Calendar - Drive)',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Google Workspace II (Chat & Space - Meet - Docs - Sheets)',
courseLevel: CourseLevel.Intermediate,
hours: '10 Horas',
},
],
},
{
title: 'Development & Scripting',
hours: '108 Horas',
children: [
{
title: 'Python 3',
courseLevel: CourseLevel.Introduction,
hours: '45 Horas',
},
{
title: 'Python 3',
courseLevel: CourseLevel.Intermediate,
hours: '45 Horas',
},
{
title: 'Bourne Again SHell (Bash)',
courseLevel: CourseLevel.Introduction,
hours: '8 Horas',
},
{
title: 'Bourne Again SHell (Bash)',
courseLevel: CourseLevel.Intermediate,
hours: '8 Horas',
},
{
title: 'Google Cloud Shell',
courseLevel: CourseLevel.Introduction,
hours: '2 Horas',
},
],
},
{
title: 'Infrastructure Orchestration',
hours: '54 Horas',
children: [
{
title: 'Terraform',
courseLevel: CourseLevel.Introduction,
hours: '20 Horas',
},
{
title: 'Terragrunt ',
courseLevel: CourseLevel.Intermediate,
hours: '20 Horas',
},
{
title: 'Pulumi ',
courseLevel: CourseLevel.Introduction,
hours: '14 Horas',
},
],
},
{
title: 'Software Control Managment',
hours: '20 Horas',
children: [
{
title: 'Git & GitHub',
courseLevel: CourseLevel.Introduction,
hours: '14 Horas',
},
{
title: 'Metodologias Ágiles',
courseLevel: CourseLevel.Introduction,
hours: '6 Horas',
},
],
},
{
title: 'Containers Orchestration',
hours: '50 Horas',
children: [
{
title: 'Docker',
courseLevel: CourseLevel.Introduction,
hours: '20 Horas',
},
{
title: 'Kubernetes',
courseLevel: CourseLevel.Introduction,
hours: '20 Horas',
},
{
title: 'Minikube ',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Continuous Integration',
hours: '28 Horas',
children: [
{
title: 'GitHub Actions',
courseLevel: CourseLevel.Introduction,
hours: '14 Horas',
},
{
title: 'GitLab CI',
courseLevel: CourseLevel.Introduction,
hours: '14 Horas',
},
],
},
{
title: 'DataBases',
hours: '10 Horas',
children: [
{
title: 'MongoDB',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Deployment Automation',
hours: '20 Horas',
children: [
{
title: 'Helm Charts',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
{
title: 'Kustomize',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
{
title: 'Public Cloud Services',
hours: '20 Horas',
children: [
{
title: 'Amazon Web Services',
courseLevel: CourseLevel.Introduction,
hours: '20 Horas',
},
],
},
{
title: 'Monitoring & Alerting',
hours: '50 Horas',
children: [
{
title: 'Prometheus',
courseLevel: CourseLevel.Introduction,
hours: '20 Horas',
},
{
title: 'Gafana',
courseLevel: CourseLevel.Introduction,
hours: '20 Horas',
},
{
title: 'AlertManager ',
courseLevel: CourseLevel.Introduction,
hours: '10 Horas',
},
],
},
];

View File

@ -1,6 +1,3 @@
export * from './aws-devops-career.ts';
export * from './cloudops-career.ts';
export * from './devops-career.ts';
export * from './features.ts';
export * from './icon-blocks.ts';
export * from './testimonials.ts';

70
src/libs/careers.ts Normal file
View File

@ -0,0 +1,70 @@
import directus from './directus';
import { readItems, type Query } from '@directus/sdk';
import type { CodyopsCareers, Careers } from '../types/codyops-careers';
import { sumTimes } from '../utils/time';
export async function getCareers(): Promise<Careers[]> {
const careers = await directus.request(
readItems<CodyopsCareers, 'codyops_careers', Query<CodyopsCareers, Careers>>('codyops_careers', {
fields: [
'slug',
'name',
'description',
'banner',
{
courses: [
{
codyops_courses_id: [
'name',
'level',
'category',
{
modules: [
'duration'
]
}
],
},
],
},
],
})
);
const careersWithCalculatedHours = careers.map(career => {
let totalCareerMinutes = 0;
const coursesWithCalculatedHours = career.courses.map(courseItem => {
const course = courseItem.codyops_courses_id;
if (course && course.modules) {
const moduleDurations = course.modules
.map(module => module.duration)
.filter((duration): duration is string => duration !== undefined && duration !== null); // Filter out undefined/null
const { hours, minutes } = sumTimes(moduleDurations);
const totalCourseMinutes = (hours * 60) + minutes;
totalCareerMinutes += totalCourseMinutes;
return {
...courseItem,
codyops_courses_id: {
...course,
totalCourseHours: hours + (minutes / 60), // Store as decimal hours
totalCourseMinutes: totalCourseMinutes,
}
};
}
return courseItem;
});
const totalCareerHours = Math.floor(totalCareerMinutes / 60);
const remainingCareerMinutes = totalCareerMinutes % 60;
return {
...career,
courses: coursesWithCalculatedHours,
totalCareerHours: totalCareerHours + (remainingCareerMinutes / 60), // Store as decimal hours
totalCareerMinutes: totalCareerMinutes,
};
});
return careersWithCalculatedHours;
}

View File

@ -1,14 +1,37 @@
import directus from "./directus";
import { readItems } from "@directus/sdk";
import { readItems, type Query } from "@directus/sdk";
import type { CodyopsCourses, Courses } from "../types/codyops-courses";
export const courses = await directus.request(
readItems("codyops_courses", {
readItems<CodyopsCourses, 'codyops_courses', Query<CodyopsCourses, Courses>>("codyops_courses", {
fields: [
'*',
'user_created.*',
'modules.*',
'codyops_careers.codyops_careers_id.name',
'codyops_careers.codyops_careers_id.slug',
{
codyops_careers: [
{
codyops_careers_id: [
'name',
'slug'
]
}
]
},
{
user_created: [
'first_name',
'last_name',
'avatar',
'url'
]
},
{
modules: [
'duration',
'title',
'description',
'video'
]
}
]}
)
);
@ -30,4 +53,3 @@ export function filterCoursesByCategory(courses: any, category: any) {
export function filterCoursesByCloud(courses: any, clouds: string[]) {
return courses.filter((course: any) => course.cloud?.includes(clouds));
};

View File

@ -30,16 +30,19 @@ type Course = {
modules: [];
}
import type { Careers } from '../types/codyops-careers';
type Schema = {
codyops_posts: Post[];
codyops_courses: Course[];
codyops_campaigns: [];
codyops_reviews: [];
codyops_currencies: [];
codyops_careers: Careers[];
}
const directus = createDirectus<Schema>(import.meta.env.ASTRO_DIRECTUS_API)
.with(staticToken(import.meta.env.ASTRO_DIRECTUS_TOKEN))
.with(rest());
export default directus;
export default directus;

View File

@ -0,0 +1,54 @@
---
import Root from '../../layouts/Root.astro';
import Main from '../../layouts/Bundle.astro';
import CareerProgram from '../../sections/global/career-program/career-program.astro';
import CareerSection from '../../sections/global/career-section/career-section.astro';
import Cta from '../../sections/global/cta/cta.astro';
import { getCareers } from '../../libs/careers';
import type { Careers } from '../../types/codyops-careers';
import { Image } from 'astro:assets';
export async function getStaticPaths() {
const careers = await getCareers();
return careers.map((career: Careers) => {
return {
params: { slug: career.slug },
props: { career },
};
});
}
interface Props {
career: Careers;
}
const { career } = Astro.props;
// Dynamically import the description and features components based on the slug
const DescriptionComponent = (await import(`../../sections/${career.slug}/${career.slug}-description.astro`)).default;
const FeaturesComponent = (await import(`../../sections/${career.slug}/${career.slug}-features.astro`)).default;
---
<Root
title={`Carrera ${career.name}`}
description={career.description}
path={`/${career.slug}/`}>
<Main className=''>
<section id="featureSection" class="content-space-t-4">
<div class="banner py-10 position-relative">
<figure class="banner__background-container-image">
<div class="overlay"></div>
<Image src={`${import.meta.env.ASTRO_ASSETS}/${career.banner}`} alt={`${career.name} image`} width={1920} height={475} />
</figure>
<FeaturesComponent />
</div>
</section>
<CareerSection>
<DescriptionComponent />
</CareerSection>
<Cta />
<CareerProgram career={career} />
<Cta />
</Main>
</Root>

View File

@ -1,35 +0,0 @@
---
import Root from '../../layouts/Root.astro';
import Main from '../../layouts/Bundle.astro';
import AwsdevopsImage from '../../assets/img/careers/awsdevops-banner-desktop.webp';
import Banner from '../../components/banner/banner.astro';
import { awsDevopsCareerProgram } from '../../data';
import AwsCloudopsDescription from '../../sections/aws-devops/aws-devops-description.astro';
import AwsDevopsFeatures from '../../sections/aws-devops/aws-devops-features.astro';
import CareerProgram from '../../sections/global/career-program/career-program.astro';
import CareerSection from '../../sections/global/career-section/career-section.astro';
import Cta from '../../sections/global/cta/cta.astro';
const programLink:string = 'https://drive.google.com/file/d/12lYUSAcN0qI9R0_N63oPTimBKIYRKOh9/preview';
---
<Root
title='Carrera AWS DevOps Engineer'
description='Nuestra carrera AWS DevOps tiene exactamente lo que necesitas para salir rapidamente al mercado laboral. Incluye los cursos mas demandados y usados en toda empresa. Nuestro programa de estudio se realizo bajo un exhaustivo trabajo de investigacion con las principales empresas del mercado mundial.'
path='/awsdevops/'>
<Main className='' id='awsdevops'>
<Banner
as="section"
id="featureSection"
className="content-space-t-4"
backgroundImage={AwsdevopsImage}
backgroundAlt="AwsDevOps image">
<AwsDevopsFeatures />
</Banner>
<CareerSection>
<AwsCloudopsDescription />
</CareerSection>
<Cta />
<CareerProgram href={programLink} list={awsDevopsCareerProgram} download="AWS Programa de carrera"/>
<Cta />
</Main>
</Root>

View File

@ -1,36 +0,0 @@
---
import Root from '../../layouts/Root.astro';
import Main from '../../layouts/Bundle.astro';
import CloudopsImage from '../../assets/img/careers/cloud-engineer-banner.webp';
import Banner from '../../components/banner/banner.astro';
import { cloudopsCareerProgram } from '../../data';
import CloudopsDescription from '../../sections/cloudops/cloudops-description.astro';
import CloudopsFeatures from '../../sections/cloudops/cloudops-features.astro';
import CareerSection from '../../sections/global/career-section/career-section.astro';
import CareerProgram from '../../sections/global/career-program/career-program.astro';
import Cta from '../../sections/global/cta/cta.astro';
const programLink:string = 'https://drive.google.com/file/d/1IQVPvhikLos-xs8s3gJazN7Nj45ZSNYL/preview';
---
<Root
title='Carrera Cloud Engineer'
description='Nuestra carrera CloudOps tiene exactamente lo que necesitas para salir rapidamente al mercado laboral. Incluye los cursos mas demandados y usados en toda empresa. Nuestro programa de estudio se realizo bajo un exhaustivo trabajo de investigacion con las principales empresas del mercado mundial.'
path='/cloudops/'>
<Main className="" id="cloudops">
<Banner
as="section"
id="featureSection"
className="content-space-t-4"
backgroundImage={CloudopsImage}
backgroundAlt="Cloudops image">
<CloudopsFeatures />
</Banner>
<CareerSection>
<CloudopsDescription />
</CareerSection>
<Cta />
<CareerProgram href={programLink} list={cloudopsCareerProgram} />
<Cta />
</Main>
</Root>

View File

@ -1,36 +0,0 @@
---
import Root from '../../layouts/Root.astro';
import Main from '../../layouts/Bundle.astro';
import DevopsImage from '../../assets/img/careers/devops-banner-desktop.webp';
import Banner from '../../components/banner/banner.astro';
import { devopsCareerProgram } from '../../data';
import DevopsDescription from '../../sections/devops/devops-description.astro';
import DevopsFeatures from '../../sections/devops/devops-features.astro';
import CareerProgram from '../../sections/global/career-program/career-program.astro';
import CareerSection from '../../sections/global/career-section/career-section.astro';
import Cta from '../../sections/global/cta/cta.astro';
const programLink:string = 'https://drive.google.com/file/d/1wz_TeKtrt_d8AHiySGDsYX1VVq48j_Ie/preview';
---
<Root
title='Carrera DevOps Engineer'
description='Nuestra carrera DevOps tiene exactamente lo que necesitas para salir rapidamente al mercado laboral. Incluye los cursos mas demandados y usados en toda empresa. Nuestro programa de estudio se realizo bajo un exhaustivo trabajo de investigacion con las principales empresas del mercado mundial.'
path='/devops/'>
<Main className='' id='devops'>
<Banner
as="section"
id="featureSection"
className="content-space-t-4"
backgroundImage={DevopsImage}
backgroundAlt="DevOps Image">
<DevopsFeatures />
</Banner>
<CareerSection>
<DevopsDescription />
</CareerSection>
<Cta />
<CareerProgram href={programLink} list={devopsCareerProgram} />
<Cta />
</Main>
</Root>

View File

@ -1,34 +1,54 @@
---
import { Accordion, CareerCourse } from '../../../types';
import { toSnakeCase } from '../../../utils';
import type { Courses } from '../../../types/codyops-courses';
import type { Careers } from '../../../types/codyops-careers';
import { toSnakeCase, fromSnakeCase } from '../../../utils';
import { slugifyCourse } from '../../../utils/text';
type Props = {
href: string;
list?: Accordion[];
download?: string;
career: Careers;
};
const { href, list, download }: Props = Astro.props;
const { career }: Props = Astro.props;
// Group courses by category
const groupedCourses = career.courses?.reduce((acc, courseItem) => {
const course = courseItem.codyops_courses_id;
if (course) {
const category = course.category;
if (!acc[category]) {
acc[category] = { courses: [], totalCategoryMinutes: 0 };
}
acc[category].courses.push(course);
if (course.totalCourseMinutes) {
acc[category].totalCategoryMinutes += course.totalCourseMinutes;
}
}
return acc;
}, {} as Record<string, { courses: Courses[]; totalCategoryMinutes: number }>);
---
<section id="program" class="container content-space-t-3 career__section">
<h2 class="career-subtitle text-white text-center mb-5">Programa de Carrera</h2>
<div class="text-center text-white mb-4">
Total Carrera: {career.totalCareerHours?.toFixed(1)} horas
</div>
<div class="accordion accordion-btn-icon-start">
{list && list.map(({children, hours, title}: Accordion) => (
{groupedCourses && Object.entries(groupedCourses).map(([category, { courses, totalCategoryMinutes }]) => (
<div class="accordion-item">
<div class="accordion-header" id="headingBasics1">
<div class="accordion-header" id={`heading-${toSnakeCase(category)}`}>
<a
class="accordion-button collapsed"
id="collab&produc_devopspage"
id={`collab&produc_devopspage-${toSnakeCase(category)}`}
role="button"
data-bs-toggle="collapse"
data-bs-target=`#${toSnakeCase(title)}`
data-bs-target={`#${toSnakeCase(category)}`}
aria-expanded="true"
aria-controls={toSnakeCase(title)}
aria-controls={toSnakeCase(category)}
>
<div class="flex-grow-1 ps-3">
<div class="row">
<div class="col-8 text-white">{title}</div>
<div class="col-8 text-white">{fromSnakeCase(category)}</div>
<!-- End Col -->
<div class="col-4 text-end">
@ -37,7 +57,7 @@ const { href, list, download }: Props = Astro.props;
<!-- End Col -->
<div class="col-lg-6">
<span class="small text-muted fw-normal">{hours}</span>
<span class="small text-white">{(totalCategoryMinutes / 60).toFixed(1)} hours</span>
</div>
<!-- End Col -->
</div>
@ -49,12 +69,12 @@ const { href, list, download }: Props = Astro.props;
</div>
</a>
</div>
<div id={toSnakeCase(title)} class="accordion-collapse collapse" aria-labelledby="headingBasics1">
<div id={toSnakeCase(category)} class="accordion-collapse collapse" aria-labelledby={`heading-${toSnakeCase(category)}`}>
<div class="accordion-body" style='margin-top: 1rem;'>
<!-- List Group -->
<div class="list-group list-group-flush list-group-no-gutters">
{
children && children.map(({courseLevel, hours, title}: CareerCourse)=>(
courses.map((course: Courses)=>(
<div class="list-group-item text-muted">
<div class="row">
<div class="col-8">
@ -63,9 +83,9 @@ const { href, list, download }: Props = Astro.props;
<i class="bi bi bi-arrow-right"></i>
</div>
<div class="flex-grow-1 ms-2">
<span class="small text-white"
>{title}
<span class="badge bg-course text-dark rounded-pill ms-1">{courseLevel}</span></span
<a href={`/cursos/${slugifyCourse(course.name+'-'+course.level)}/`} class="small text-white"
>{course.name}
<span class="badge bg-course text-dark rounded-pill ms-1">{course.level}</span></a
>
</div>
</div>
@ -78,7 +98,7 @@ const { href, list, download }: Props = Astro.props;
<!-- End Col -->
<div class="col-lg-6">
<span class="small">{hours}</span>
<span class="small text-white">{course.totalCourseHours?.toFixed(1)} hours</span>
</div>
<!-- End Col -->
</div>
@ -97,15 +117,5 @@ const { href, list, download }: Props = Astro.props;
</div>
))}
<div class="container text-center mt-5">
<a
class="btn btn-primary btn-transition fw-bold"
id="downloadprogram_devopspage"
href={href}
style="border-radius: 0.9rem"
download={download}
>Descargar Programa Completo
</a>
</div>
</div>
</section>

View File

@ -1,4 +1,4 @@
import type { CareerCourse } from '@/types/career.ts';
import type { CareerCourse } from './career.ts';
type Position = 'left' | 'right';

View File

@ -1,4 +1,4 @@
import type { CourseLevel, PriceBox } from '@/enums';
import type { CourseLevel, PriceBox } from '../enums';
export type Career = {
image: any;

View File

@ -0,0 +1,25 @@
import type { Courses } from './codyops-courses';
import type { Users } from './codyops-users';
export interface Careers {
id: string;
slug: string;
status: string;
sort: number | null;
user_created: Users;
user_updated: Users;
date_created: string | null;
date_updated: string | null;
name: string;
description: string;
banner: string;
courses: {
codyops_courses_id: Courses;
}[];
totalCareerHours?: number;
totalCareerMinutes?: number;
}
export interface CodyopsCareers {
codyops_careers: Careers;
}

View File

@ -0,0 +1,34 @@
import type { Modules } from "./codyops-modules";
import type { Careers } from "./codyops-careers";
import type { Users } from "./codyops-users";
export interface Courses {
id: string;
status: string;
sort: number | null;
user_created?: Partial<Users>;
user_updated: Users;
date_created: string | null;
date_updated: string | null;
name: string;
description: string;
level: string;
type: string;
category: string;
language: string;
features: string;
version: string;
image: string;
modules?: Partial<Modules>[];
content: string;
cloud: string;
codyops_careers?: {
codyops_careers_id?: Partial<Careers>;
};
totalCourseHours?: number;
totalCourseMinutes?: number;
}
export interface CodyopsCourses {
codyops_courses: Courses[];
}

View File

@ -0,0 +1,15 @@
export interface Modules {
id: string;
status: string;
sort: number | null;
user_created: string | null;
user_updated: string | null;
date_created: string | null;
date_updated: string | null;
title: string;
description: string;
video_theory: string;
video_practice: string;
duration: string;
courses_id: string;
}

View File

@ -0,0 +1,6 @@
export interface Users {
first_name: string;
last_name: string;
avatar: string;
url: string;
}

View File

@ -1,4 +1,4 @@
import type { Accordion } from '@/types/accordion.ts';
import type { Accordion } from './accordion.ts';
export type Course = {
title: string;

View File

@ -1,28 +1,26 @@
import type { CollectionEntry } from 'astro:content';
import type { Courses } from '../types/codyops-courses';
export const getUniqueCategories = (
courses: Array<CollectionEntry<'courses'>>
courses: Courses[]
) => {
const coursesEntries = courses.flatMap(
// @ts-ignore
// eslint-disable-next-line no-unsafe-optional-chaining
(course: CollectionEntry<'courses'>) => [...course?.data?.categories]
(course: Courses) => [...course.category]
);
return [...new Set(coursesEntries)];
};
export const getUniqueLevels = (courses: CollectionEntry<'courses'>[]) => {
export const getUniqueLevels = (courses: Courses[]) => {
const coursesEntries = courses.map(
(course: CollectionEntry<'courses'>) => course.data.level
(course: Courses) => course.level
);
return [...new Set(coursesEntries)];
};
export const sortCoursesByDate = (posts: CollectionEntry<'courses'>[]) =>
posts
.filter(({ data }) => !data?.draft)
export const sortCoursesByDate = (courses: Courses[]) =>
courses
.filter((course) => course.status !== 'draft') // Assuming 'draft' status is used instead of a 'draft' property
.sort(
(a, b) =>
Math.floor(new Date(b?.data?.date).getTime() / 1000) -
Math.floor(new Date(a?.data?.date).getTime() / 1000)
Math.floor(new Date(b.date_created || '').getTime() / 1000) -
Math.floor(new Date(a.date_created || '').getTime() / 1000)
);

View File

@ -1,3 +1,3 @@
export const fromSnakeCase = (text: any) => {
return text.replace(/-/g, ' ').replace(/\b\w/g, (word) => word.toUpperCase());
export const fromSnakeCase = (text: string) => {
return text.replace(/-/g, ' ').replace(/\b\w/g, (word: string) => word.toUpperCase());
};

View File

@ -1,23 +1,35 @@
function parseTime(time) {
const [hours, minutes, milliseconds] = time.split(':').map(Number);
interface TimeParts {
hours: number;
minutes: number;
milliseconds: number;
}
interface TimeResult {
hours: number;
minutes: number;
}
function parseTime(time: string | null | undefined): TimeParts {
const safeTime = time || '0:0:0'; // Provide a default string if time is null/undefined/empty
const [hours, minutes, milliseconds] = safeTime.split(':').map(Number);
return { hours, minutes, milliseconds };
}
function timeToMilliseconds({ hours, minutes, milliseconds }) {
function timeToMilliseconds({ hours, minutes, milliseconds }: TimeParts): number {
return (hours * 60 * 60 * 1000) + (minutes * 60 * 1000) + milliseconds;
}
function millisecondsToTime(ms) {
function millisecondsToTime(ms: number): TimeResult {
const totalMinutes = Math.floor(ms / (60 * 1000));
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return { hours, minutes };
}
export function sumTimes(times) {
export function sumTimes(times: string[]): TimeResult {
const totalMilliseconds = times
.map(parseTime)
.map(timeToMilliseconds)
.reduce((acc, ms) => acc + ms, 0);
.reduce((acc: number, ms: number) => acc + ms, 0);
return millisecondsToTime(totalMilliseconds);
}