diff --git a/SITES.md b/SITES.md
index c00b693e..6829c970 100644
--- a/SITES.md
+++ b/SITES.md
@@ -14,6 +14,7 @@
| antennaeurope.gr | 1 | 1 | 🟢 | |
| antennapacific.gr | 1 | 1 | 🟢 | |
| antennasatellite.gr | 1 | 1 | 🟢 | |
+ | app.tvufop.com.br | 1 | 1 | 🟢 | |
| arianatelevision.com | 1 | 1 | 🟢 | |
| arirang.com | 3 | 3 | 🟢 | |
| artonline.tv | 5 | 5 | 🟢 | |
diff --git a/sites/app.tvufop.com.br/app.tvufop.com.br.channels.xml b/sites/app.tvufop.com.br/app.tvufop.com.br.channels.xml
new file mode 100644
index 00000000..cc2338e5
--- /dev/null
+++ b/sites/app.tvufop.com.br/app.tvufop.com.br.channels.xml
@@ -0,0 +1,7 @@
+
+
+
+
+ TV UFOP
+
+
diff --git a/sites/app.tvufop.com.br/app.tvufop.com.br.config.js b/sites/app.tvufop.com.br/app.tvufop.com.br.config.js
new file mode 100644
index 00000000..a113981b
--- /dev/null
+++ b/sites/app.tvufop.com.br/app.tvufop.com.br.config.js
@@ -0,0 +1,167 @@
+const { File } = require('node:buffer')
+
+
+if (typeof global.File === 'undefined') {
+
+ global.File = File
+
+}
+
+
+const cheerio = require('cheerio')
+
+
+module.exports = {
+
+ site: 'app.tvufop.com.br',
+
+ days: 7,
+
+ url() {
+
+ return 'https://app.tvufop.com.br/epg/epg_tvufop_web.xml'
+
+ },
+
+ parser({ content, channel, date }) {
+
+ const $ = cheerio.load(content || '', { xmlMode: true, decodeEntities: false })
+
+ const programs = []
+
+
+ const dayStart = date.startOf('d').toDate()
+
+ const dayEnd = date.add(1, 'd').startOf('d').toDate()
+
+
+ $(`programme[channel="${channel.site_id}"]`).each((_, el) => {
+
+ const $el = $(el)
+
+
+ const start = parseXmltvDate($el.attr('start'))
+
+ const stop = parseXmltvDate($el.attr('stop'))
+
+
+ if (!start || !stop) return
+
+ if (start >= dayEnd || stop <= dayStart) return
+
+
+ const title = textOf($el, 'title')
+
+ if (!title) return
+
+
+ const item = {
+
+ title,
+
+ start,
+
+ stop
+
+ }
+
+
+ const description = textOf($el, 'desc')
+
+ if (description) item.description = description
+
+
+ const icon = $el.find('icon').attr('src')
+
+ if (icon) item.icon = icon
+
+
+ const rating = $el.find('rating > value').first().text().trim()
+
+ if (rating) item.rating = rating
+
+
+ programs.push(item)
+
+ })
+
+
+ return programs
+
+ }
+
+}
+
+
+function textOf($el, tagName) {
+
+ return $el.find(tagName).first().text().trim()
+
+}
+
+
+function parseXmltvDate(value) {
+
+ if (!value) return null
+
+
+ const m = value.trim().match(
+
+ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\s+([+-])(\d{2})(\d{2})$/
+
+ )
+
+
+ if (!m) return null
+
+
+ const [
+
+ ,
+
+ year,
+
+ month,
+
+ day,
+
+ hour,
+
+ minute,
+
+ second,
+
+ sign,
+
+ tzHour,
+
+ tzMinute
+
+ ] = m
+
+
+ const utcMs = Date.UTC(
+
+ Number(year),
+
+ Number(month) - 1,
+
+ Number(day),
+
+ Number(hour),
+
+ Number(minute),
+
+ Number(second)
+
+ )
+
+
+ const offsetMinutes =
+
+ (Number(tzHour) * 60 + Number(tzMinute)) * (sign === '+' ? 1 : -1)
+
+
+ return new Date(utcMs - offsetMinutes * 60 * 1000)
+
+}
diff --git a/sites/app.tvufop.com.br/app.tvufop.com.br.test.js b/sites/app.tvufop.com.br/app.tvufop.com.br.test.js
new file mode 100644
index 00000000..3111ae9f
--- /dev/null
+++ b/sites/app.tvufop.com.br/app.tvufop.com.br.test.js
@@ -0,0 +1,97 @@
+
+const { parser, url } = require('./app.tvufop.com.br.config.js')
+
+const dayjs = require('dayjs')
+
+const utc = require('dayjs/plugin/utc')
+
+const customParseFormat = require('dayjs/plugin/customParseFormat')
+
+
+
+dayjs.extend(utc)
+
+dayjs.extend(customParseFormat)
+
+
+
+const date = dayjs.utc('2026-03-28', 'YYYY-MM-DD').startOf('d')
+
+const channel = {
+
+ site_id: 'TVUFOP.br@HD',
+
+ xmltv_id: 'TVUFOP.br@HD',
+
+ lang: 'pt'
+
+}
+
+
+
+it('can generate valid url', () => {
+
+ expect(url({ channel, date })).toBe('https://app.tvufop.com.br/epg/epg_tvufop_web.xml')
+
+})
+
+
+
+it('can parse response', () => {
+
+ const content = `
+
+
+
+
+
+ TV UFOP
+
+
+
+
+
+ (FUTURA) CANAL DA HISTÓRIA - CARMEN MIRANDA
+
+ Clara e Neto usam uma máquina do tempo.
+
+
+
+ Livre
+
+
+
+ `
+
+
+
+ const results = parser({ content, channel, date })
+
+
+
+ expect(results.length).toBe(1)
+
+ expect(results[0]).toMatchObject({
+
+ title: '(FUTURA) CANAL DA HISTÓRIA - CARMEN MIRANDA',
+
+ description: 'Clara e Neto usam uma máquina do tempo.',
+
+ icon: 'https://app.tvufop.com.br/epg/CANAL.jpg',
+
+ rating: 'Livre'
+
+ })
+
+})
+
+
+
+it('can handle empty guide', () => {
+
+ const results = parser({ content: '', channel, date })
+
+ expect(results).toMatchObject([])
+
+})
+
diff --git a/sites/app.tvufop.com.br/readme.md b/sites/app.tvufop.com.br/readme.md
new file mode 100644
index 00000000..a4e61539
--- /dev/null
+++ b/sites/app.tvufop.com.br/readme.md
@@ -0,0 +1,31 @@
+
+# app.tvufop.com.br
+
+
+
+https://app.tvufop.com.br
+
+
+
+### Download the guide
+
+
+
+Run:
+
+
+
+npm run grab --- --site=app.tvufop.com.br
+
+
+
+### Test
+
+
+
+Run:
+
+
+
+npm test --- app.tvufop.com.br
+