diff --git a/scripts/proximo-feriado.js b/scripts/proximo-feriado.js index 0571c29..9372a77 100644 --- a/scripts/proximo-feriado.js +++ b/scripts/proximo-feriado.js @@ -1,150 +1,112 @@ + // Description: -// Dice cuándo es el feriado mas próximo en Chile +// Retorna cuándo es el feriado más próximo en Chile usando feriados.devschile.cl // Dependencies: -// none - -// Configuration: -// none +// moment // Commands: -// hubot proximo feriado - Retorna la cantidad de días, la fecha y el motivo del próximo feriado en Chile -// hubot próximo feriado - Retorna la cantidad de días, la fecha y el motivo del próximo feriado en Chile +// hubot proximo feriado - Retorna la fecha y motivo del próximo feriado en Chile +// hubot próximo feriado - Retorna la fecha y motivo del próximo feriado en Chile // Author: -// @victorsanmartin - -// Co-Author: // @jorgeepunan -// @raerpo const moment = require('moment') -// Constants -const SATURDAY_ISO_DAY = 6 -const SUNDAY_ISO_DAY = 7 +const nerdMessages = [ + 'Está tan lejos que puedes terminar un sprint, refactorizar legacy y aún así no ver la luz del sol.', + 'Falta tanto que tu backlog va a crecer más rápido que tu motivación por vivir.', + 'Hay más chances de que te pidan deploy en viernes que de ver ese feriado pronto.', + 'Puedes aprender un nuevo framework, olvidarlo y aún así no llega el feriado.', + 'Falta tanto que hasta el código espagueti tiene tiempo de fermentar.', + '¿Pensando en vacaciones? Mejor piensa en unit tests, el feriado no viene pronto.', + 'El próximo feriado está tan lejos que podrías terminar todos los tickets y aún así seguir esperando.' +] function humanizeMonth (month) { - const monthNumber = month - 1 const monthNames = [ - 'Enero', - 'Febrero', - 'Marzo', - 'Abril', - 'Mayo', - 'Junio', - 'Julio', - 'Agosto', - 'Sedtiembre', - 'Octubre', - 'Noviembre', - 'Diciembre' + 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', + 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre' ] - - return monthNames[monthNumber] + return monthNames[parseInt(month, 10) - 1] } function humanizeDay (day) { const dayNames = ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'] - return dayNames[day] } -const getOutputMessage = (holiday, days, { isWorkDay = true }) => { - if (holiday === undefined) { - return +function sarcasticWeekendMessage (weekday) { + if (weekday === 6) { + return '¡Qué suerte! El próximo feriado es sábado. ¡A disfrutar tu día libre proletario, como si fuera diferente a cualquier otro sábado!' + } + if (weekday === 0) { + return '¡Genial! El próximo feriado es domingo. El descanso del proletario, igual que todos los domingos, ¡no te emociones mucho!' } - const date = new Date(`${holiday.fecha}T00:00:00-04:00`) - const humanDate = holiday.fecha.split('-') - const humanDay = humanDate[2].replace(/^0+/, '') - const humanMonth = humanDate[1] - const humanWeekDay = humanizeDay(date.getDay()) - const message = `${holiday.nombre} (_${holiday.tipo.toLowerCase()}_)` - const plural = days > 1 ? ['n', 's'] : ['', ''] - const mensajeInicial = isWorkDay ? 'El próximo feriado es el' : 'El próximo feriado para los :gonzaleee: es el' - return `${mensajeInicial} *${humanWeekDay} ${humanDay} de ${humanizeMonth(humanMonth)}*, queda${ - plural[0] - } *${days} día${plural[1]}*. Se celebra: *${message}*` + return null +} + +function normalizeHolidays (feriadosData) { + const feriadosPorMes = feriadosData.feriados || {} + const feriados = Object.values(feriadosPorMes).reduce((acc, arr) => acc.concat(arr), []) + return feriados.map(f => { + const mes = String(f.mes).padStart(2, '0') + const dia = String(f.dia).padStart(2, '0') + return { + ...f, + date: `${feriadosData.year}-${mes}-${dia}`, + title: f.descripcion + } + }) } -const getNextWorkingHoliday = (holidays, refDate) => { - if (holidays.length === 0) { - return null +function buildNextHolidayMessage (feriadosData, today, randomFn = Math.random) { + const feriadosConFecha = normalizeHolidays(feriadosData) + const nextHoliday = feriadosConFecha.find(h => moment(h.date).isSameOrAfter(today, 'day')) + if (!nextHoliday) { + return 'No hay más feriados este año. ¡A trabajar, proletario!' } - const futureHolidays = holidays.filter(holiday => moment(`${holiday.fecha}T00:00:00-04:00`).isAfter(moment(refDate))) - if (futureHolidays[0].isWorkDay) { - return futureHolidays[0] - } else { - // eslint-disable-next-line no-unused-vars - const [_, ...nextHolidays] = futureHolidays - return getNextWorkingHoliday(nextHolidays, refDate) + const holidayDate = moment(nextHoliday.date) + const daysDiff = holidayDate.diff(today, 'days') + const dayOfWeek = holidayDate.day() + const humanDate = `${humanizeDay(dayOfWeek)} ${holidayDate.date()} de ${humanizeMonth(holidayDate.format('MM'))}` + let message = `El próximo feriado es el *${humanDate}* (${nextHoliday.title}). Quedan *${daysDiff} día${daysDiff === 1 ? '' : 's'}*.` + const sarcastic = sarcasticWeekendMessage(dayOfWeek) + if (sarcastic) { + message += `\n${sarcastic}` + } else if (dayOfWeek >= 1 && dayOfWeek <= 5 && daysDiff > 14) { + const randomMsg = nerdMessages[Math.floor(randomFn() * nerdMessages.length)] + message += `\n${randomMsg}` } + return message } -exports.getNextWorkingHoliday = getNextWorkingHoliday - module.exports = function (robot) { robot.respond(/pr(o|ó)ximo feriado/i, function (msg) { - const today = new Date( - [ - new Date().getFullYear(), - ('0' + (new Date().getMonth() + 1)).slice(-2), - ('0' + new Date().getDate()).slice(-2) - ].join('-') + 'T00:00:00-04:00' - ) - const currentYear = new Date().getFullYear() - - robot.http(`https://apis.digital.gob.cl/fl/feriados/${currentYear}`).get()(function (err, res, body) { + const today = moment() + const currentYear = today.year() + robot.http(`https://feriados.devschile.cl/api/holidays/${currentYear}`).get()(function (err, res, body) { if (err || res.statusCode !== 200) { - return robot.emit('error', err || new Error(`Status code ${res.statusCode}`), msg, 'proximo-feriado') + return msg.send('No se pudo obtener la información de los feriados. Intenta más tarde.') } - - const bodyParsed = JSON.parse(body) - - // Filter out data from past days - const nextHolidays = bodyParsed.filter(holiday => { - const date = new Date(`${holiday.fecha}T00:00:00-04:00`) - return moment(date).isSame(today) || moment(date).isAfter(today) - }) - - // No more holidays for this year :depressed: - if (nextHolidays.length === 0) { - msg.send('No hay más feriados este año :depressed:') - } else if (nextHolidays.length > 0) { - /** - * extend the holidays data with the property if it's a working day. I couldn't find a better name - * so I leave a comment instead - */ - const extendedHolidays = nextHolidays.map(holiday => { - const date = moment(`${holiday.fecha}T00:00:00-04:00`) - const isWorkingDay = date.isoWeekday() !== SATURDAY_ISO_DAY && date.isoWeekday() !== SUNDAY_ISO_DAY - return { ...holiday, isWorkingDay } - }) - /** - * print the next holiday and in case of being a non-working day show the next one - */ - for (let i = 0; i <= extendedHolidays.length; i++) { - const holiday = extendedHolidays[i] - const date = moment(`${holiday.fecha}T00:00:00-04:00`) - const message = `${holiday.nombre} (_${holiday.tipo.toLowerCase()}_)` - if (moment(date).isSame(today)) { - msg.send(`*¡HOY es feriado!* Se celebra: *${message}*. ¡Disfrútalo!`) - } else if (moment(date).isAfter(today)) { - if (holiday.isWorkDay) { - msg.send(getOutputMessage(holiday, date.diff(today, 'days'), { isWorkDay: true })) - } else { - msg.send(getOutputMessage(holiday, date.diff(today, 'days'), { isWorkDay: true })) - const nextHoliday = getNextWorkingHoliday(extendedHolidays, holiday.fecha) - if (nextHoliday) { - msg.send( - getOutputMessage(nextHoliday, moment(nextHoliday.fecha).diff(today, 'days'), { isWorkDay: false }) - ) - } - } - break - } - } + let feriadosData + try { + feriadosData = JSON.parse(body) + } catch (e) { + return msg.send('Error al procesar la información de los feriados.') } + const message = buildNextHolidayMessage(feriadosData, today) + msg.send(message) }) }) } + +module.exports._test = { + humanizeMonth, + humanizeDay, + sarcasticWeekendMessage, + normalizeHolidays, + buildNextHolidayMessage, + nerdMessages +} diff --git a/scripts/yodev.js b/scripts/yodev.js index 1b97659..b3f2bf9 100644 --- a/scripts/yodev.js +++ b/scripts/yodev.js @@ -63,47 +63,27 @@ module.exports = (robot) => { } const limitedJobs = jobs.slice(0, 5) - const blocks = [] - blocks.push({ - type: 'section', - text: { - type: 'mrkdwn', - text: `✅ Encontré ${totalJobs} resultado(s) en yodev.dev:` - } - }) + const messages = limitedJobs.map((job) => { + const lines = [` +*${job.title}*`, + `*Empresa:* ${job.company}`, + `*Ubicación:* ${job.location}`, + `*Postulación:* ${job.url}`, + `*Publicado:* ${job.posted || job.date || ''}` + ] - limitedJobs.forEach((job) => { - blocks.push({ - type: 'divider' - }) + if (job.salary) lines.push(`*Sueldo:* ${job.salary}`) + if (job.type) lines.push(`*Tipo:* ${job.type}`) + if (job.source) lines.push(`*Fuente:* ${job.source}`) - blocks.push({ - type: 'section', - text: { - type: 'mrkdwn', - text: `*<${job.url}|${job.title}>*\n* · Empresa:* ${job.company}\n* · Ubicación:* ${job.location}\n* · Publicado:* ${job.posted || job.date || ''}${job.salary ? `\n* · Sueldo:* ${job.salary}` : ''}${job.type ? `\n* · Tipo:* ${job.type}` : ''}${job.source ? `\n* · Fuente:* ${job.source}` : ''}` - } - }) + return lines.join('\n') }) if (viewMoreUrl) { - blocks.push({ - type: 'divider' - }) - - blocks.push({ - type: 'section', - text: { - type: 'mrkdwn', - text: `🔍 ¿Quieres ver más ofertas? Visita: <${viewMoreUrl}>` - } - }) + messages.push(`\n🔍 ¿Quieres ver más ofertas? Visita: ${viewMoreUrl}`) } - robot.adapter.client.web.chat.postMessage({ - channel: res.message.room, - blocks - }) + send(`✅ Encontré ${totalJobs} resultado(s) en yodev.dev:\n\n${messages.join('\n\n---\n\n')}`) } catch (error) { robot.logger.error(`Error al parsear la respuesta: ${error.message}`) send('Ups! Ocurrió un error al procesar las ofertas de trabajo de yodev.dev. 😱') diff --git a/test/proximo-feriado.test.js b/test/proximo-feriado.test.js index 4fb6078..952eebe 100644 --- a/test/proximo-feriado.test.js +++ b/test/proximo-feriado.test.js @@ -1,105 +1,79 @@ -/* eslint-disable no-global-assign */ +'use strict' + +require('coffee-script/register') const test = require('ava') const Helper = require('hubot-test-helper') -const nock = require('nock') -const sinon = require('sinon') +const moment = require('moment') const helper = new Helper('../scripts/proximo-feriado.js') - -const HOLIDAYS_2022 = [ - { nombre: 'A\u00f1o Nuevo', comentarios: null, fecha: '2022-01-01', irrenunciable: '1', tipo: 'Civil' }, - { nombre: 'Viernes Santo', comentarios: null, fecha: '2022-04-15', irrenunciable: '0', tipo: 'Religioso' }, - { nombre: 'Sabado Santo', comentarios: null, fecha: '2022-04-16', irrenunciable: '0', tipo: 'Religioso' }, - { - nombre: 'D\u00eda Nacional del Trabajo', - comentarios: null, - fecha: '2022-05-01', - irrenunciable: '1', - tipo: 'Civil' - }, - { - nombre: 'D\u00eda de las Glorias Navales', - comentarios: null, - fecha: '2022-05-21', - irrenunciable: '0', - tipo: 'Civil' - }, - { - nombre: 'D\u00eda Nacional de los Pueblos Ind\u00edgenas', - comentarios: null, - fecha: '2022-06-21', - irrenunciable: '0', - tipo: 'Civil' - }, - { nombre: 'San Pedro y San Pablo', comentarios: null, fecha: '2022-06-27', irrenunciable: '0', tipo: 'Religioso' }, - { - nombre: 'D\u00eda de la Virgen del Carmen', - comentarios: null, - fecha: '2022-07-16', - irrenunciable: '0', - tipo: 'Religioso' - }, - { - nombre: 'Asunci\u00f3n de la Virgen', - comentarios: null, - fecha: '2022-08-15', - irrenunciable: '0', - tipo: 'Religioso' - }, - { nombre: 'Independencia Nacional', comentarios: null, fecha: '2022-09-18', irrenunciable: '1', tipo: 'Civil' }, - { - nombre: 'D\u00eda de las Glorias del Ejercito', - comentarios: null, - fecha: '2022-09-19', - irrenunciable: '1', - tipo: 'Civil' - }, - { nombre: 'Encuentro de Dos Mundos', comentarios: null, fecha: '2022-10-10', irrenunciable: '0', tipo: 'Civil' }, - { - nombre: 'D\u00eda de las Iglesias Evang\u00e9licas y Protestantes', - comentarios: null, - fecha: '2022-10-31', - irrenunciable: '0', - tipo: 'Religioso' - }, - { - nombre: 'D\u00eda de Todos los Santos', - comentarios: null, - fecha: '2022-11-01', - irrenunciable: '0', - tipo: 'Religioso' - }, - { - nombre: 'Inmaculada Concepci\u00f3n', - comentarios: null, - fecha: '2022-12-08', - irrenunciable: '0', - tipo: 'Religioso' - }, - { nombre: 'Navidad', comentarios: null, fecha: '2022-12-25', irrenunciable: '1', tipo: 'Religioso' } -] +const { _test } = require('../scripts/proximo-feriado.js') +const sleep = m => new Promise(resolve => setTimeout(resolve, m)) test.beforeEach(t => { - nock('https://apis.digital.gob.cl') - .get('/fl/feriados/2022') - .reply(200, HOLIDAYS_2022) t.context.room = helper.createRoom({ httpd: false }) + t.context.realNow = moment.now + moment.now = () => new Date(2026, 1, 1, 12, 0, 0, 0).valueOf() }) test.afterEach(t => { + moment.now = t.context.realNow t.context.room.destroy() }) -test.cb('debe mostrar que el dia es feriado cuando la fecha es 21-06-2022 y el siguiente feriado', t => { - const clock = sinon.useFakeTimers({ now: new Date(2022, 5, 21).getTime(), shouldAdvanceTime: true }) +test('buildNextHolidayMessage agrega sarcasmo de fin de semana', t => { + const feriadosData = { + year: 2026, + feriados: { + '02': [ + { mes: 2, dia: 7, descripcion: 'Día de prueba' } + ] + } + } + const today = moment('2026-02-01') + const message = _test.buildNextHolidayMessage(feriadosData, today) + + t.true(message.includes('Sábado 7 de Febrero')) + t.true(message.includes('Día de prueba')) + t.true(message.includes('¡Qué suerte! El próximo feriado es sábado.')) +}) + +test('buildNextHolidayMessage agrega mensaje nerd determinístico', t => { + const feriadosData = { + year: 2026, + feriados: { + '03': [ + { mes: 3, dia: 10, descripcion: 'Día de pruebas largas' } + ] + } + } + const today = moment('2026-02-01') + const message = _test.buildNextHolidayMessage(feriadosData, today, () => 0) + + t.true(message.includes(_test.nerdMessages[0])) + t.true(message.includes('Día de pruebas largas')) +}) + +test('responde con el próximo feriado desde la API', async t => { + const feriadosData = { + year: 2026, + feriados: { + '02': [ + { mes: 2, dia: 7, descripcion: 'Día de prueba' } + ] + } + } + + t.context.room.robot.http = () => ({ + get: () => (cb) => cb(null, { statusCode: 200 }, JSON.stringify(feriadosData)) + }) + t.context.room.user.say('user', 'hubot proximo feriado') - setTimeout(() => { - t.deepEqual(t.context.room.messages, [ - ['user', 'hubot proximo feriado'], - ['hubot', '*¡HOY es feriado!* Se celebra: *Día Nacional de los Pueblos Indígenas (_civil_)*. ¡Disfrútalo!'], - ['hubot', 'El próximo feriado es el *Lunes 27 de Junio*, quedan *6 días*. Se celebra: *San Pedro y San Pablo (_religioso_)*'] - ]) - t.end() - clock.restore() - }, 500) + await sleep(300) + + const expected = _test.buildNextHolidayMessage(feriadosData, moment()) + + t.deepEqual(t.context.room.messages, [ + ['user', 'hubot proximo feriado'], + ['hubot', expected] + ]) })