Stack tecnológico
Dev & Deploy| Herramienta | Uso |
|---|---|
Vite | Bundler y dev server. Multi-entrada (una por cada HTML). |
Three.js | Escena 3D en la entrada principal (index.html). |
Mercado Pago SDK | Pagos de entradas. Cargado en index.html vía CDN. |
Vite PWA | Service worker e instalación como app. Estrategia: injectManifest. |
Vercel | Hosting, routing y funciones serverless (api/). |
Mux | Video streaming y gestión de assets de video. |
ElevenLabs | Text-to-Speech del AI voice en plantform. |
Google Gemini | IA de voz en plantform. |
Zoho | Email de notificaciones transaccionales. |
Dev local
Dev & Deploynpm install
npm run dev
Corre en http://localhost:5000. También accesible desde la red local.
npx vite --port 5173 --open wiki.html
Build & Deploy
Dev & DeployEl deploy automático ocurre en cada git push a main. Vercel detecta el push y hace el build solo.
Deploy manual
npm run build # genera /dist
vercel --prod # deploy inmediato
Regenerar páginas estáticas (SEO)
Después de modificar un proyecto en projects.json:
npm run gen
# equivale a: node generate-static.js
# genera public/[slug]/index.html con OG tags por cada proyecto
Routing
Dev & DeployEl routing se define en vercel.json. Hay un catch-all al final que manda cualquier slug a plantform.html.
Regla crítica: cualquier ruta nueva debe ir en vercel.json antes del catch-all /:slug([a-z0-9][a-z0-9-]*). Si va después, la absorbe plantform.
| URL | Archivo |
|---|---|
/ | index.html — entrada 3D + paywall |
/audiont | audiont.html |
/trabajos | trabajos.html |
/tv | tv.html |
/turing | turing.html |
/nota-constante | nota-constante.html |
/otro-dia-en-la-red | otro-dia-en-la-red.html |
/wiki | wiki.html |
/:slug | plantform.html — proyectos y talentos dinámicos |
Variables de entorno
Dev & DeployTruco clave: nunca usar echo ni Write-Output para pasar el valor — agregan un newline que corrompe la clave. Siempre usar node -e "process.stdout.write(...)".
# Agregar variable (sin newline al final)
node -e "process.stdout.write('EL_VALOR')" | npx vercel env add NOMBRE_VAR production
# Eliminar variable
node -e "process.stdout.write('y')" | npx vercel env rm NOMBRE_VAR production
# Listar variables actuales
npx vercel env ls
# Redesplegar después de cualquier cambio
npx vercel --prod
Variables activas
| Variable | Descripción |
|---|---|
GEMINI_API_KEY | Google Gemini — IA de voz en plantform |
ELEVENLABS_API_KEY | ElevenLabs — TTS en plantform |
ELEVENLABS_VOICE_ID | ID de voz ElevenLabs en plantform |
MP_ACCESS_TOKEN | MercadoPago — token de producción (backend) |
VITE_MP_PUBLIC_KEY | MercadoPago — public key para el frontend |
ENV_MP_PUBLIC_KEY | MercadoPago — public key para serverless |
ZOHO_EMAIL | Email Zoho para notificaciones |
ZOHO_PASSWORD | Contraseña Zoho |
MUX_TOKEN_ID | Mux — token ID |
MUX_TOKEN_SECRET | Mux — token secret |
Agregar proyecto
ContenidoCrear public/[slug]/ y copiar:
thumb.jpg— 1280×720poster.jpg— 1920×1080
También se puede usar URLs externas (CDN, Webflow) — poner la URL completa en el JSON.
public/data/projects.json
{
"name": "Nombre del Proyecto",
"slug": "nombre-del-proyecto",
"description": "Descripción corta (cards + SEO).",
"contenido": "<p>Cuerpo HTML. Puede tener iframes.</p>",
"card": "/nombre-del-proyecto/thumb.jpg",
"banner": "/nombre-del-proyecto/poster.jpg",
"thumbnail": "/nombre-del-proyecto/thumb.jpg",
"talentos": ["slug-talento-1"],
"formato": "1",
"credits": "<h4>Dirección</h4><p>Nombre Apellido</p>",
"web": "https://url-externa.com"
}
npm run gen
Genera public/[slug]/index.html con OG tags por proyecto.
git add public/data/projects.json public/[slug]/
git commit -m "feat: agregar [Nombre del proyecto]"
git push
IDs de formato
Agregar talento
ContenidoLos talentos se cargan desde public/data/talentos.json. El slug debe existir ahí antes de referenciarlo en un proyecto.
{
"name": "Nombre Apellido",
"slug": "nombre-apellido",
"rol": "Director / Editor / ...",
"bio": "Bio corta.",
"foto": "/nombre-apellido/foto.jpg"
}
Agregar página HTML nueva
ContenidoMUNDO-TOXI/nombre-pagina.html
vite.config.js
Sin esto, Vite no lo buildea y no existe en producción.
// vite.config.js → build.rollupOptions.input
nombrePagina: resolve(__dirname, 'nombre-pagina.html'),
vercel.json
Tiene que ir antes del catch-all /:slug.
{ "source": "/nombre-pagina", "destination": "/nombre-pagina.html" }
vercel --prod
Catálogo de proyectos
ContenidoTodos los proyectos en public/data/projects.json. Accesibles en toxi.media/[slug].
Toxi TV — web
Streaming & TVEl canal en vivo de toxi.media/tv. Todos los usuarios ven lo mismo en el mismo momento, sin señal en tiempo real.
Cómo funciona la sincronización
- El video actual se calcula usando
EPOCH_SEC = 2026-01-01T00:00:00Zcomo referencia. - Se suma el tiempo desde el epoch y se divide entre la duración total de la playlist para encontrar el slot actual.
- Cada cliente lo calcula de forma independiente — no hay WebSocket ni señal en tiempo real.
Actualizar la playlist
Hay que editar dos archivos en paralelo:
src/content.js— agregar entrada cononTV: true(para la web).api/playlist.mjs— agregar el mismo item al arrayPLAYLIST(para la app Android y el AI voice).
api/ai-voice.mjs importa PLAYLIST directamente desde api/playlist.mjs — no hay que tocar ai-voice.mjs al agregar contenido.
Endpoint
GET https://toxi.media/api/playlist
# devuelve: [{ "id": "MuxPlaybackID", "duration": 339.5, "title": "Título" }, ...]
App Android TV
Streaming & TVApp para Android TV / Google TV. Reproduce el canal sincronizado, igual que la versión web.
Estructura
toxi-tv-android/
app/src/main/java/media/toxi/tv/
MainActivity.kt # ExoPlayer + fetch de playlist al arrancar
Playlist.kt # Playlist hardcodeada + lógica de slot sincronizado
app/src/main/res/
drawable/banner.png # Banner launcher TV (320×180)
app/build.gradle.kts
Cómo funciona
- Al arrancar busca
https://toxi.media/api/playlisty reemplaza la hardcodeada. - Si falla el fetch, usa
HARDCODED_PLAYLISTenPlaylist.kt. - Cada 30 segundos corrige el drift — si la diferencia es mayor de 5 segundos, hace seek.
- Los cambios en la playlist se propagan sin actualizar el APK.
Build y distribución
toxi-tv-android/ en Android Studio..aab a Play Console → Toxi Media TV.Streaming local con Icecast
Streaming & TVTres piezas: PC (fuente de audio) → BUTT (encoder) → Icecast (servidor).
1 — Icecast (Windows)
- Descargar de icecast.org e instalar.
- Abrir
icecast.xmly agregar CORS (necesario para la onda visual):
<http-headers>
<header name="Access-Control-Allow-Origin" value="*" />
</http-headers>
2 — BUTT (Broadcast Using This Tool)
Descargar de danielnoethen.de/butt. Settings → Main → Server → Add:
| Campo | Valor |
|---|---|
| Type | Icecast |
| Address | localhost |
| Port | 8000 |
| User | source (no "admin") |
| Password | hackme (default — cambiar en xml) |
| Mountpoint | /stream |
3 — Conectar en el código
// src/line.js
const STREAM_URL = 'http://localhost:8000/stream';
Troubleshooting
| Problema | Solución |
|---|---|
| No entra a localhost:8000 | La ventana de consola de Icecast debe estar abierta. Probar 127.0.0.1:8000. |
| No se escucha nada | Verificar que BUTT muestre tiempo corriendo. Abrir la URL del stream directo en el browser. |
| Onda visual plana | Problema de CORS. Revisar el icecast.xml del paso 1. |
| Error Mixed Content | Si la web está en https://, el stream no puede ser http://. Ver la guía de streaming a internet. |
Streaming a internet (HTTPS)
Streaming & TVEl problema: el sitio usa HTTPS (Vercel). Icecast local usa HTTP. Los browsers bloquean audio HTTP dentro de páginas HTTPS — "Mixed Content".
Solución recomendada: Zeno.fm (gratis)
En el panel obtener: Server Address, Port, Mount Point, Password.
Reemplazar localhost por stream.zeno.fm y el resto de los datos del panel.
STREAM_URL con la URL HTTPS que da Zeno:
const STREAM_URL = 'https://stream.zeno.fm/tu-codigo';
Alternativa temporal: Ngrok
Para testear con alguien sin registrarse. La URL cambia en cada reinicio.
ngrok http 8000
# usar la URL https://xyz.ngrok-free.app/stream
Estructura de archivos
PlataformaMUNDO-TOXI/
index.html # Entrada principal (keypad + paywall + Three.js)
plantform.html # Página dinámica de proyectos y talentos
tv.html # Canal en vivo sincronizado
audiont.html # Landing de Audion't
wiki.html # Este wiki
[proyecto].html # Páginas estáticas por proyecto especial
src/
main.js # Escena Three.js
paywall.js # Lógica de pagos y acceso
style.css # Estilos globales
content.js # Contenido de TV (items con onTV: true)
public/
data/
projects.json # Catálogo completo de proyectos
talentos.json # Catálogo de talentos
formatos.json # Categorías (Cine, VR, Software, etc.)
[slug]/ # Imágenes y index.html SEO por proyecto
api/
playlist.mjs # Playlist de TV para app Android y AI voice
ai-voice.mjs # AI voice — importa playlist.mjs directo
transcribe.mjs # Transcripción de audio (maxDuration: 60s)
tools/
upload-to-mux.js # Subir video a Mux
upload-downloads-batch.js # Upload en batch
toxi-tv-android/ # App Android TV (Kotlin + ExoPlayer)
_archive/ # Archivos viejos preservados
vite.config.js # Bundler: entradas HTML + PWA config
vercel.json # Routing, rewrites, funciones serverless
generate-static.js # Genera meta tags SEO por proyecto
Assets públicos
PlataformaTodo lo que está en public/ se sirve desde la raíz del sitio en dev y en producción. No hace falta importarlos con JS.
public/hedonismo-y-seduccion/thumb.jpg
# accesible en: /hedonismo-y-seduccion/thumb.jpg
Vite no procesa los archivos de public/ — se copian tal cual al dist/. Ideal para imágenes grandes, audio y videos que no necesitan hashing de nombre.
SEO y meta tags
PlataformaLas páginas de proyectos en plantform.html son SPA (JS renderiza el contenido). Para que Google y WhatsApp lean los meta tags correctos, se genera un index.html estático por proyecto.
Generar
npm run gen
# genera public/[slug]/index.html con:
# <title> del proyecto
# og:title, og:description, og:image
# twitter:card
Vercel sirve ese HTML estático cuando Google o un scraper accede a /[slug]. El browser lo recibe, carga el JS de plantform y renderiza la página completa.
Siempre correr npm run gen después de modificar name, description, card o banner en projects.json.
Equipo humano
Equipo & GearCore — decisiones estratégicas
| Nombre | Rol |
|---|---|
| Juan Manuel Cafferata | Co-fundador · dueño de la marca · decisiones estratégicas |
| Fabrizio Sanguinetti | Co-fundador · dueño de la marca · decisiones estratégicas |
| Sofía Nicola Patrón | Capitalista · socia |
Líderes de área
| Nombre | Área |
|---|---|
| Fermín Delía | VR — producción |
| Julián Toninello | VR — exhibición |
| Juan Segundo Quiroga | Series — líder |
| Nicolás Martorell | Programación — líder |
Pasantes
| Nombre | Área | Tipo |
|---|---|---|
| Luis Fernando Guerra Velarde | VR | Pasante |
| Martín Pollo | VR | Pasante |
| Ximena Colombres | Periodismo | Práctica profesional |
Cámaras & video
Equipo & Gear| Equipo | Detalle |
|---|---|
| Sony A7 III | Full frame mirrorless · cuerpo principal |
| Sony A7 IV | Full frame mirrorless · cuerpo secundario / B-cam |
| DJI Ronin SC | Gimbal de mano para ambas cámaras |
| Reflector LED 200W | Iluminación principal |
| Reflector LED 100W | Fill / lateral |
| Reflector LED 50W | Kicker / backlight |
| Pantalla verde / chroma | Tela backdrop en la oficina |
| Citroën Picasso Xsara | Transporte de equipos a locaciones |
VR — headsets
Equipo & Gear| Equipo | Cantidad | Uso |
|---|---|---|
| Meta Quest 2 | ×5 | Exhibición de experiencias VR |
| Meta Quest 3S | ×2 | Desarrollo y exhibición premium |
Responsables: Fermín Delía (producción) · Julián Toninello (exhibición).
Audio
Equipo & GearInterfaz & grabadores
| Equipo | Detalle |
|---|---|
| Focusrite Scarlett i2 | Interfaz de audio 2 entradas · uso en estudio y set |
| Zoom H4n | Grabador de campo portátil · 4 pistas |
Micrófonos
| Equipo | Tipo | Uso |
|---|---|---|
| Samson C01 | Condensador de diafragma grande | Voz, instrumento en estudio |
| Rode NTG1 | Shotgun / cardioide | Audio en cámara, entrevistas, set |
Instrumentos
| Instrumento | Modelo / detalle |
|---|---|
| Piano eléctrico | Yamaha P90 |
| Guitarras | ×2 |
| Violín | ×1 |
| Clarinete | ×1 |
| Trompeta | ×1 |
Infraestructura
Equipo & GearOficina
| Espacio | Dirección |
|---|---|
| Sede principal | Rodríguez Peña 158 · Buenos Aires |
Computadoras
| Equipo | Cantidad | Uso |
|---|---|---|
| Dell XPS 15 | ×2 | Edición, desarrollo, renders livianos |
| PC Gamer — NVIDIA RTX 1060 | ×2 | Renders pesados, desarrollo VR, 3D en tiempo real |