// ScreenOthers.jsx — news, chat, incident, about, profile, auth, settings /* -------- Shared: Report Card -------- */ function ReportCard({ report, isDesktop = false, onLike, onSpam }) { const kindMeta = { delay: { label: 'Demora', color: '#F5B301' }, cut: { label: 'Corte', color: '#CC3333' }, vandal: { label: 'Vandalismo', color: '#A72126' }, access: { label: 'Accesibilidad', color: '#0078B1' }, safety: { label: 'Seguridad', color: '#52267D' }, overcrowd: { label: 'Aglomeración', color: '#E13A40' }, cleanliness: { label: 'Limpieza', color: '#006515' }, other: { label: 'Otro', color: '#8A97A8' }, }; const meta = kindMeta[report.kind] || kindMeta.other; return (
{meta.label} {report.station} {report.ago}
{report.text}
{report.author}
{/* Actions */}
{!report.mine && ( )} {report.mine && Tu reporte}
); } /* -------- News -------- */ function ScreenNews({ go, t }) { const [filter, setFilter] = React.useState('all'); const items = filter === 'all' ? NEWS : NEWS.filter(n => n.line === filter); const [statusEvents, setStatusEvents] = React.useState([]); React.useEffect(() => { loadStatusEvents(5).then(evs => setStatusEvents(evs || [])); }, []); const filteredEvents = filter === 'all' ? statusEvents : statusEvents.filter(e => e.lineId === filter); return (
go('main')} style={{width:36,height:36,borderRadius:18,background:'var(--ink-100)',display:'flex',alignItems:'center',justifyContent:'center'}}>} /> {/* Tab bar — Reportes comunidad temporalmente oculto */}
{true && ( /* solo noticias — reportes comunidad temporalmente ocultos */ <> {/* line filter chips */}
{LINES.map(l => ( ))}
{/* Cambios de estado reales — últimos 5 días de infosubte */} {filteredEvents.length === 0 ? (
Sin novedades en los últimos 5 días{filter !== 'all' ? ` para Línea ${filter}` : ''}.
) : (
{filteredEvents.map((ev, i) => )}
)} {/* Anuncios históricos del CMS / automáticos como contexto adicional */} {(() => { const automáticos = getAnnouncementHistory(3, 3).filter(a => filter === 'all' || a.lineId === filter); let cms_hist = []; try { if (window.CMS_ANNOUNCEMENTS && Array.isArray(window.CMS_ANNOUNCEMENTS)) { cms_hist = window.CMS_ANNOUNCEMENTS; } } catch {} const hist = [...automáticos, ...cms_hist].slice(0, 3); if (!hist.length) return null; return (
Anuncios
{hist.map(a => { const meta = ANNOUNCEMENT_KIND[a.kind] || ANNOUNCEMENT_KIND.advisory; return (
{meta.label} {a.lineId && } {a.source && · {a.source.label}{a.source.handle ? ` ${a.source.handle}` : ''}} {timeAgoLabel(a.createdAt)}
{a.text}
); })}
); })()}
)} {/* reportes comunidad temporalmente ocultos */}
); } /* -------- Chat / Assistant -------- */ function ScreenChat({ go, t }) { const [msgs, setMsgs] = React.useState([ { role: 'bot', text: '¡Hola! Soy tu asistente de viaje. Puedo ayudarte con horarios, combinaciones y reportes. ¿A dónde vas?' }, ]); const [input, setInput] = React.useState(''); const [loading, setLoading] = React.useState(false); const send = async () => { if (!input.trim()) return; const userMsg = input; setMsgs(m => [...m, { role: 'user', text: userMsg }]); setInput(''); setLoading(true); try { const reply = await window.claude.complete(`Sos un asistente amable del subte de Buenos Aires (alSubte). Respondé en 1-2 oraciones cortas, en español rioplatense. Pregunta del usuario: ${userMsg}`); setMsgs(m => [...m, { role: 'bot', text: reply }]); } catch { setMsgs(m => [...m, { role: 'bot', text: 'No pude conectarme. Probá de nuevo en un ratito.' }]); } setLoading(false); }; const suggestions = ['¿Cómo llego a Retiro?', 'Combinación Línea B con D', 'Estaciones accesibles']; return (
go('main')} style={{width:36,height:36,borderRadius:18,background:'var(--ink-100)',display:'flex',alignItems:'center',justifyContent:'center'}}>} />
{msgs.map((m, i) => (
{m.role === 'bot' &&
al
}
{m.text}
))} {loading &&
escribiendo...
}
{suggestions.map(s => ( ))}
setInput(e.target.value)} onKeyDown={e=>e.key==='Enter'&&send()} placeholder={t('askMe')} style={{ flex: 1, padding: '10px 14px', borderRadius: 20, border: '1px solid var(--border)', background: 'var(--ink-050)', outline: 'none' }} />
); } /* -------- Incident Report -------- */ const TERMS_KEY = 'report_terms_v1_accepted'; const EMOVA_KINDS = new Set(['vandal','access','cleanliness','other']); const EMOVA_HOUR_OK = () => { const h = new Date().getHours(), d = new Date().getDay(); return d >= 1 && d <= 5 && h >= 8 && h < 20; }; function ScreenIncidentReport({ go, t, lang }) { // terms acceptance (localStorage) const [termsAccepted, setTermsAccepted] = React.useState(() => !!localStorage.getItem(TERMS_KEY)); // mobile check: if window width > 900 we show a soft warning (not a hard block — could be tablet) const isNarrow = window.innerWidth <= 900; // report flow state const [step, setStep] = React.useState(1); // 1=category 2=line 3=details 4=done const [escalate, setEscalate] = React.useState(null); // 'safety' | 'emova' | null const [kind, setKind] = React.useState(null); const [line, setLine] = React.useState(null); const [desc, setDesc] = React.useState(''); const reset = () => { setStep(1); setKind(null); setLine(null); setDesc(''); setEscalate(null); }; const acceptTerms = () => { localStorage.setItem(TERMS_KEY, JSON.stringify({ acceptedAt: new Date().toISOString(), version: 1 })); setTermsAccepted(true); }; const selectKind = (id) => { setKind(id); if (id === 'safety') { setEscalate('safety'); } else if (EMOVA_KINDS.has(id)) { setEscalate('emova'); } else { setStep(2); } }; // ── Terms warning modal ────────────────────────────────────────────────────── if (!termsAccepted) return (
go('main')} style={{width:36,height:36,borderRadius:18,background:'var(--ink-100)',display:'flex',alignItems:'center',justifyContent:'center'}}>} />

Información importante

¿QUÉ SON LOS REPORTES?
Los reportes son colaborativos y pueden no reflejar el estado oficial del servicio. alSubte no es un canal oficial y no está vinculado con Emova, SBASE ni el Gobierno de la Ciudad.
DATOS QUE RECOLECTAMOS
Al enviar un reporte registramos: tu identidad de Google, tu ubicación en el momento del reporte, el modelo de dispositivo y sistema operativo.

La ubicación sólo se usa para validar el reporte y no se comparte con terceros. No almacenamos tu posición fuera de los reportes.
REQUISITOS
• Debés tener al menos 16 años.
• Debés iniciar sesión con Google.
• Solo se aceptan reportes desde dispositivo móvil.
• Información falsa puede resultar en la suspensión de tu cuenta.
EMERGENCIAS
Ante una emergencia, riesgo de vida o delito en curso, llamá al 911. alSubte no reemplaza los servicios de emergencias.
TUS DERECHOS
Podés solicitar la eliminación de tus datos en cualquier momento desde Ajustes → Datos y privacidad, o escribiéndonos a bear.by.patagonia at gmail dot com (Ley 25.326, art. 16).
Al aceptar confirmás que tenés al menos 16 años y aceptás los{' '} Términos y Condiciones y la{' '} Política de Privacidad de alSubte. Versión 1.0 — 2026-04-21.
); // ── Desktop soft-warning ───────────────────────────────────────────────────── if (!isNarrow) return (
go('main')} style={{width:36,height:36,borderRadius:18,background:'var(--ink-100)',display:'flex',alignItems:'center',justifyContent:'center'}}>} />
Usá el celular
Los reportes requieren verificar tu ubicación en tiempo real. Abrí alSubte desde tu teléfono para enviar un reporte.
); // ── Safety escalation interstitial ────────────────────────────────────────── if (escalate === 'safety') return (
{setEscalate(null);setKind(null);}} style={{width:36,height:36,borderRadius:18,background:'var(--ink-100)',display:'flex',alignItems:'center',justifyContent:'center'}}>} />

Incidente de seguridad

¿El incidente requiere atención inmediata?
Ante riesgo de vida, agresión o delito en curso
no esperes — llamá al 911 directamente.
O informá a la comunidad sin urgencia
); // ── Emova escalation interstitial ─────────────────────────────────────────── if (escalate === 'emova') { const kindLabel = INCIDENT_TYPES.find(it=>it.id===kind)?.label || 'Incidente'; return (
{setEscalate(null);setKind(null);}} style={{width:36,height:36,borderRadius:18,background:'var(--ink-100)',display:'flex',alignItems:'center',justifyContent:'center'}}>} />
Este tipo de reporte puede ser atendido directamente por Emova, la empresa concesionaria. Te ofrecemos tres canales de contacto:
{/* Canal 1 — email */}
Escribir a Emova
info@emova.com.ar
{/* Canal 2 — web */}
Canal de denuncias web
emova.com.ar · formulario oficial
{/* Canal consultas generales — Flokzu */}
Consultas Generales
Formulario de contacto · alSubte
{/* Canal 3 — teléfono (solo Lun-Vie 8-20) */}
Llamar a Emova
0800-333-6682 {EMOVA_HOUR_OK() ? · Disponible ahora : · Lun–Vie 8:00 a 20:00}
También podés informar a la comunidad
); } // ── Main report flow ───────────────────────────────────────────────────────── return (
1 ? ()=>setStep(s=>s-1) : ()=>go('main')} style={{width:36,height:36,borderRadius:18,background:'var(--ink-100)',display:'flex',alignItems:'center',justifyContent:'center'}}>} />
{step < 4 &&
{[1,2,3].map(i =>
)}
} {step === 1 && ( <>

{t('whatHappened')}

Los reportes son colaborativos y ayudan a otros pasajeros.
{INCIDENT_TYPES.map(it => ( ))}
Los reportes no reflejan el estado oficial · Para emergencias llamá al 911
)} {step === 2 && ( <>

{t('whichLine')}

Elegí la línea afectada.
{LINES.map(l => ( ))}
)} {step === 3 && ( <>

Contanos más

Opcional, pero ayuda.
Detectando ubicación
Cerca de {line ? 'estación Pueyrredón' : 'Av. Corrientes 1500'}
cambiar