Capítulo social de la guía Neblla — amigos y comunicaciones dev↔usuario. El contrato base está en core.txt; el criterio de cuándo usar Neblla, en empezar.txt. # Social ## Amigos Las amistades son entre personas (cross-app), pero todo lo que la app ve está filtrado por app: un amigo solo aparece si concedió el scope `discoverable` para *esta* app, y "online" significa "conectado a esta app ahora mismo". Requiere el scope `friends` en el login. ```javascript console.log(neblla.social.friends); // [{ id, name, picture, online, ... }, ...] ``` Métodos: ```javascript neblla.social.request(targetUserId); // mandar petición a otro usuario online en esta app neblla.social.accept(requestId); neblla.social.reject(requestId); neblla.social.remove(friendId); // funciona offline neblla.social.inviteFriend(targetUserId); // "ven a mi sala" (cualquier tipo de sala) neblla.social.inviteToRoom(targetUserId); // añade a la whitelist de tu sala privada await neblla.rooms.direct(amigo); // «Escribir»: la sala privada de los dos (receta en salas.txt) await neblla.db.dataOf(amigo); // lo suyo que tu app deja ver a sus amigos (identidad.txt, «Lo que ven otros usuarios») await neblla.db.offer({ to: amigo, give: [{ table: 'cartas', id }], get: [] }); // regalarle una carta: la acepta y es suya (identidad.txt, «Intercambios entre usuarios») await neblla.social.list(); // refresh manual de social.friends y social.requests; devuelve los amigos ``` Para añadir a alguien que **no** está en la app ahora mismo, usa su código de amistad (su handle público de Neblla): `neblla.social.request({ code })` le deja la petición en su buzón aunque esté desconectado, y la verá en `social.requests` la próxima vez que abra una app con el scope `friends` (`target_not_found` si el código no existe, `self` si es el tuyo). `await neblla.social.code()` devuelve el código del usuario para que lo comparta (`null` si aún no ha elegido handle en play.neblla.com). Y el enlace `https://play.neblla.com/u/` abre la pestaña de amigos de su cuenta Neblla con el código ya puesto: un clic para mandar la petición. Eventos: `neblla.social.on('request')` (`{ requestId, from }`), `on('accepted')`, `on('removed')`, `on('updated')` (`{ friends }`), y la invitación a una sala, `neblla.rooms.on('invite')` (`{ from, roomName, appId, type }`). Propiedades: `neblla.social.friends` (amigos visibles en esta app), `neblla.social.requests` (peticiones recibidas). ## Invitar a un amigo de Neblla A LA APP Distinto de `social.inviteFriend` (que es "ven a mi sala" dentro de la app). Aquí el usuario trae a un amigo que aún no conoce la app: ```javascript await neblla.social.inviteToApp(); // requiere usuario logueado ``` - Abre un popup alojado por Neblla donde el usuario elige a un amigo de su lista — la app **nunca** ve esa lista, ni quién fue elegido. - Fire-and-forget: ni el invitador ni la app saben si el amigo estaba online o si le llegó. - No requiere el scope `friends`. - El amigo ve la invitación en su bandeja en play.neblla.com (caduca a los 5 minutos) con un enlace que lo trae a la app con `?nbla_invite=`. Cuando alguien abre la app desde una invitación (o cuando un amigo lo invita estando ya dentro), el SDK rellena `neblla.social.invitedBy` (`{ id, name, picture, slug, userId|null }` — `userId` es el id por-app del que invitó si está online ahora) y dispara `neblla.social.on('appInvite')` (`{ from }`). Si el invitado luego hace login, su amigo aparece en `neblla.social.friends` (si concedió `discoverable`), y con `neblla.social.invitedBy.userId` se le puede meter a una sala al instante. ## Comunicaciones dev↔usuario Canal para que el usuario hable con el desarrollador sin salir de la app. Requiere login. Es el módulo `inbox`: `neblla_update_app` con `features: { inbox: true }` — ese es el interruptor que comprueba el servidor, y sin él `inbox.send` responde `feature_disabled`. El campo `inbox` de primer nivel NO es el interruptor: lleva el tope por usuario (`{ maxPerUser }`). Un buzón guarda como máximo **100 mensajes por usuario en gratis y 2000 en Pro**; el dev puede bajarlo pero no subirlo por encima del techo de su plan, y pasarse responde `communications_full`. ```javascript const { communication } = await neblla.inbox.send({ message: 'Encontré un typo en la pantalla de resultados', data: { type: 'bug' } }); ``` Firma completa: `neblla.inbox.send({ message, data?, dateFrom?, dateTo?, thread? })` **(CLIENT)**. El desarrollador lo ve en el backoffice de developers.neblla.com — con el id del usuario, la fecha, la categoría asignada en `data.type` — y puede responder desde allí. El evento `inbox.on('updated')` avisa en cliente de cualquier cambio en tiempo real: ```javascript neblla.inbox.on('updated', ({ communication, action }) => { if (action === 'created' && communication.from === 'dev') { showToast('Tienes respuesta del desarrollador'); } }); ``` Otros métodos: - `neblla.inbox.update(commId, {...})` **(CLIENT)** — edita una comunicación propia (mientras no esté marcada como leída). - `neblla.inbox.delete(id)` **(CLIENT)** — borra la suya. Eventos: `neblla.inbox.on('messages')` **(CLIENT)** — sync inicial; `neblla.inbox.on('updated')` **(CLIENT)** — `{ communication, action }`. Propiedad: `neblla.inbox.messages` **(CLIENT)** — array de comunicaciones del usuario. Para escribir a todos tus usuarios a la vez (o a un grupo), con el coste dicho antes de enviar: identidad.txt, «Escribir a todos tus usuarios». **Respuestas guardadas.** Lo que el dev contesta a menudo («Horarios de las clases») lo guarda una vez y lo elige al responder en el panel (Personas › Buzón › Respuestas guardadas, y «Respuestas guardadas ▾» encima de la caja de respuesta). Viven en la configuración de la app, `savedReplies: [{ id, title, text }]` (hasta 50; título de hasta 80 caracteres y texto de hasta 5000), y son de la app, no del borrador: cambian en el acto, en la publicada y en pre, sin publicar nada. El usuario nunca ve la lista, solo el texto que le mandas. Tu IA las lee en `neblla_get_app` (`app.savedReplies`) y las cambia con `neblla_update_app({ savedReplies })`, que reemplaza la lista entera (conserva el `id` de las que se quedan; `null` las borra todas; `invalid_saved_replies` si alguna no cabe): ```javascript // neblla_update_app { appId: 'ABC', savedReplies: [ { title: 'Horarios de las clases', text: 'Las clases son los lunes y miércoles a las 18:00. Se reservan en la app, en Clases.' }, ] } ``` **Gente sin cuenta: el formulario de contacto de tu web.** Quien no tiene cuenta en tu app también puede escribirte, desde un formulario en tu web: un script y un enlace, sin programar nada más. ```html Escríbenos ``` El enlace abre el formulario (email, nombre y mensaje, en el idioma de la página) y el mensaje entra en Personas › Buzón como el de cualquier usuario, en un hilo por email marcado «sin cuenta», con su aviso en Inicio (`inbox_contact`) hasta que lo lees o contestas. Lo que contestas le llega **por email**, desde Neblla, con Reply-To a la dirección que elijas (`neblla_update_app({ inbox: { replyTo: 'hola@tuweb.com' } })`, o en Personas › Buzón; vacío, tu email): si te responde, te llega a ti. `neblla_list_communications` marca esos mensajes con `contact: true`, y `neblla_send_communication` a ese `userId` manda el email (`communication.emailed` dice si salió). Solo vale desde una página de tu app (sus dominios permitidos, `.neblla.app` o play.neblla.com) y con el módulo `inbox` encendido (si no, `inbox_off`); hay un límite por dirección IP y 200 mensajes al día por app. Alguien sin cuenta no es un usuario: no sale en Personas › Usuarios, ni en los recuentos, ni en las exportaciones, ni le llega «Escribir a todos». Y aunque luego se registre con ese email, su hilo sigue aparte: escribir un email en un formulario no prueba que sea suyo.