-
Notifications
You must be signed in to change notification settings - Fork 354
Community Apps
This page links to applications built by the community and posted to the news group. Post to the appjs forum to get your application included in the list.
-
(win/mac/linux) appjs-TiddlyWiki TiddlyWiki running in appjs
-
(win/mac/linux) Creating Your First AppJS App with Custom Chrome Tutorial and github project
-
(linux) bringing html5 to the desktop Tutorial and code
-
(win) Task Monitor CPU Usage, RAM Status and System Uptime
-
(win) King James Bible Site Specific Browser for King James Bible
-
(win) Quran software on the Quran where users can read and listen to the Quran being read
-
GitGui git client written in appjs
-
osx-bootstrap osx appjs with twitter bootstrap
-
(win/mac/linux) Brackets A fork of Brackets which runs on appjs
-
(win) WorldAddresses International address lookup client (registration required before download)
-
(mac) Mac_AppMaker Creates layout for a native mac application (beta)
-
(mac) drag0n PHP based package manager. Calls php via cgi
import React, { useMemo, useState, useCallback } from "react"; import { SafeAreaView, View, Text, FlatList, TouchableOpacity, StyleSheet, StatusBar, } from "react-native";
// ----------------------------- // Datos de ejemplo Saufy TV // ----------------------------- const VIDEOS = [ { id: "1", titulo: "Juventud y liderazgo en Ecuador", descripcion: "Conversación sobre liderazgo juvenil, política y participación ciudadana en Ecuador.", tags: ["juventud", "liderazgo", "ecuador", "politica"], duracionMin: 42, fechaPublicacion: "2025-11-10", viewsTotales: 1250, likesTotales: 210, tipo: "podcast_largo", invitado: "Líder juvenil AIESEC", }, { id: "2", titulo: "Historias desde el pupito del mundo", descripcion: "Relatos íntimos de vida, resiliencia y sueños contados desde los buses y las calles de Quito.", tags: ["historias", "vida", "quito", "emocional"], duracionMin: 35, fechaPublicacion: "2025-11-15", viewsTotales: 890, likesTotales: 150, tipo: "podcast_largo", invitado: "Saúl Chisaguano", }, { id: "3", titulo: "Sexo, cuerpo y tabúes: hablemos sin miedo", descripcion: "Episodio sobre educación sexual, tabúes y cómo comunicarnos con responsabilidad.", tags: ["sexualidad", "cuerpo", "tabu", "educacion"], duracionMin: 55, fechaPublicacion: "2025-11-12", viewsTotales: 1670, likesTotales: 320, tipo: "podcast_largo", invitado: "Invitada especialista", }, { id: "4", titulo: "Clip: ¿Qué es ser líder en 30 segundos?", descripcion: "Clip corto sobre la esencia del liderazgo en contextos juveniles.", tags: ["liderazgo", "juventud", "clip"], duracionMin: 1, fechaPublicacion: "2025-11-16", viewsTotales: 3200, likesTotales: 680, tipo: "clip", invitado: "Líder juvenil AIESEC", }, { id: "5", titulo: "Emprender desde cero con 2 maletas", descripcion: "Historia de emprendimiento, fracasos y comenzar de nuevo con casi nada.", tags: ["emprendimiento", "historias", "motivacion"], duracionMin: 48, fechaPublicacion: "2025-11-08", viewsTotales: 980, likesTotales: 190, tipo: "podcast_largo", invitado: "Emprendedor invitado", }, { id: "6", titulo: "Clip: Un consejo para no rendirte hoy", descripcion: "Mensaje corto para quienes están a punto de rendirse con sus proyectos.", tags: ["motivacion", "clip", "vida"], duracionMin: 2, fechaPublicacion: "2025-11-17", viewsTotales: 4100, likesTotales: 910, tipo: "clip", invitado: "Saúl Chisaguano", }, ];
// Usuario simulado (luego esto se puede conectar a auth real) const USUARIO_INICIAL = { id: "user-1", nombre: "Saúl", interesesIniciales: [ "juventud", "liderazgo", "historias", "emprendimiento", "sexualidad", ], };
// ----------------------------- // Helpers // -----------------------------
function diasDesde(fechaStr) { const hoy = new Date(); const fecha = new Date(fechaStr); const diffMs = hoy.getTime() - fecha.getTime(); return Math.floor(diffMs / (1000 * 60 * 60 * 24)); }
function normalizarCampo(lista, campo) { const valores = lista.map((v) => v[campo]); const max = Math.max(...valores); const min = Math.min(...valores); if (max === min) { return {}; // todos iguales, no aporta } const resultado = {}; lista.forEach((v) => { resultado[v.id] = (v[campo] - min) / (max - min); }); return resultado; }
// ----------------------------- // Lógica de recomendación // -----------------------------
function obtenerInteresesDesdeHistorial(historial, videos) { const contadorTags = {}; historial.forEach((item) => { const video = videos.find((v) => v.id === item.idVideo); if (!video) return; video.tags.forEach((tag) => { if (!contadorTags[tag]) contadorTags[tag] = 0; contadorTags[tag] += 1; }); });
const pares = Object.entries(contadorTags); pares.sort((a, b) => b[1] - a[1]); // orden descendente return pares.slice(0, 5).map(([tag]) => tag); }
function recomendarVideos(usuario, historial, videos) { const vistos = new Set(historial.map((h) => h.idVideo)); const interesesUsuario = historial.length > 0 ? obtenerInteresesDesdeHistorial(historial, videos) : usuario.interesesIniciales;
const viewsNorm = normalizarCampo(videos, "viewsTotales"); const likesNorm = normalizarCampo(videos, "likesTotales");
const puntuados = videos.map((video) => { let score = 0;
// 1. Coincidencia con intereses
const tagsEnComun = video.tags.filter((t) =>
interesesUsuario.includes(t)
).length;
score += tagsEnComun * 3;
// 2. Popularidad
const vNorm = viewsNorm[video.id] ?? 0;
const lNorm = likesNorm[video.id] ?? 0;
score += vNorm * 2;
score += lNorm * 2;
// 3. Novedad
const dias = diasDesde(video.fechaPublicacion);
if (dias < 3) score += 5;
else if (dias < 7) score += 3;
else if (dias < 30) score += 1;
// 4. Afinidad por invitado en historial
const haVistoMismoInvitado = historial.some((h) => {
const vHist = videos.find((v) => v.id === h.idVideo);
return vHist && vHist.invitado === video.invitado;
});
if (haVistoMismoInvitado) score += 4;
// 5. Penalizar si ya visto casi completo (simulado con flag)
const r