Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/desarrolladorandres2026-gif/Native-tailwind/llms.txt

Use this file to discover all available pages before exploring further.

Debuta is composed of four runtime components — a React Native mobile app, a Node.js/Express REST API, a Socket.io real-time server, and a vanilla-JS admin panel — all backed by MongoDB Atlas for persistence and Cloudinary for binary file storage. Every component communicates through a single Express server process: REST over HTTP and events over WebSockets on the same port.

Component Diagram

┌─────────────────────────────────┐
│      Mobile App (Expo RN)       │
│  iOS · Android                  │
│                                 │
│  axios (REST)  socket.io-client │
└────────┬────────────┬───────────┘
         │ HTTP/REST  │ WebSocket
         ▼            ▼
┌─────────────────────────────────┐
│   Express Server  (port 3000)   │
│                                 │
│  /api/*  routes    Socket.io    │◄──── /admin (static files)
│  JWT middleware    onlineUsers  │           │
│  Multer uploads    pendingCalls │     Admin Panel (browser)
└────────┬────────────┬───────────┘
         │            │
         ▼            ▼
   MongoDB Atlas   Cloudinary
   (Mongoose ODM)  (images/files)
The Express HTTP server and the Socket.io server share the same port. There is no separate WebSocket port to open in your firewall or configure in a reverse proxy. In production, set ALLOWED_ORIGINS to restrict both REST CORS and Socket.io CORS to your known origins.

JWT Authentication Flow

All protected REST routes run the verificarToken middleware from src/middlewares/auth.middleware.js. The mobile client stores the token in AsyncStorage under the key access_token and attaches it to every request via an Axios interceptor:
// components/services/api.ts — request interceptor
const token = await AsyncStorage.getItem('access_token');
if (token) config.headers['Authorization'] = `Bearer ${token}`;
The middleware on the server side validates the token and loads the full user document:
// src/middlewares/auth.middleware.js
const token   = header.split(' ')[1];           // strip "Bearer "
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const usuario = await Usuario.findById(decoded.id)
  .select('+social_friend_ids interests ciudad birth_date settings rol activo latitude longitude');
req.usuario = usuario;                           // available to all downstream handlers
Socket.io authentication follows the same pattern but uses the handshake payload instead of an HTTP header:
// Client — before calling socket.connect()
socket.auth = { token: accessToken };

// Server — src/socket.js middleware
const token   = socket.handshake.auth?.token;
const decoded = jwt.verify(token, process.env.JWT_SECRET);
socket.usuarioId = decoded.id;
An invalid or missing token causes the socket connection to be refused before any event handlers run.

Role-Based Middleware

Three middleware functions gate access beyond basic authentication:
MiddlewareAllowed roleHTTP status on failure
verificarTokenAny authenticated user401 Unauthorized
soloAdminadmin403 Forbidden
soloAsociadoasociado403 Forbidden

API Route Map

All application routes are mounted under the /api prefix in src/app.js. The admin panel is served separately as static files.
Mount pathRoute filePurpose
/apiauth.routes.jsRegister, login, and token management
/api/usersuser.routes.jsProfile CRUD, discovery / swipe feed
/api/matchesmatch.routes.jsMatch listing, unmatch, date suggestion state
/api/chatchat.routes.jsMessage history (REST fallback for Socket.io)
/api/settingssettings.routes.jsNotification and privacy preferences
/api/likeslikes.routes.jsRetrieve users who liked the current user
/api/reportreport.routes.jsSubmit and manage user reports
/api/facialfacial.routes.jsFacial verification upload and status
/api/postspost.routes.jsSocial-wall posts (create, list, react)
/api/adminadmin.routes.jsAdmin-only user/content management
/api/asociadoasociado.routes.jsRestaurant partner profile and photo management
/api/authsocial.routes.jsGoogle and Facebook OAuth token exchange
/api/passwordpassword.routes.jsForgot-password email and reset flow
/api/soportesoporte.routes.jsSupport ticket creation and status
/adminStatic files (admin/)Vanilla-JS admin panel HTML/CSS/JS
/healthInline handlerHealth check — returns { status: "ok" }
A 404 handler at the bottom of app.js catches any unmatched path and returns { "message": "Ruta no encontrada" }. This applies only to paths not covered by the route table above.

Socket.io Architecture

The Socket.io server is initialised in src/socket.js and attached to the same Node.js http.Server instance that Express runs on. Two in-memory data structures drive real-time features:
StructureTypeContents
onlineUsersMap<string, Date>Maps a userId string to the Date of their socket connection
pendingCallsMap<string, object>Stores call payloads for offline users; entries expire after 60 seconds
When a socket authenticates and connects, the server immediately joins it to a personal room named user:<userId>. Targeted emissions (messages, call signals, date suggestions) always use this room so a user can have multiple sockets (e.g. tablet + phone) and receive every event.

Socket.io Events

DirectionEventPayload summaryDescription
Client → Servermensaje:enviar{ paraId, content }Send a text message to a matched user
Server → Clientmensaje:nuevoFull message document + sender infoDelivered to both sender and receiver rooms
Client → Servermensajes:leer{ deId }Mark all messages from deId as read
Client → Serverpresencia:estado{ userId }Ask whether a user is currently online
Server → Clientpresencia:respuesta{ userId, online, lastSeen }Response to presencia:estado
Server → Allusuario:online{ userId }Broadcast when a user connects
Server → Allusuario:offline{ userId, lastSeen }Broadcast when a user disconnects
Client → Servercall:request{ paraId, signalData, isVideo, callerName, callerPhoto }Initiate a WebRTC call
Server → Clientcall:incoming{ fromId, signalData, isVideo, callerName, callerPhoto }Notify receiver of an incoming call
Server → Clientcall:waiting{ paraId, reason: "offline" }Notify caller the receiver is offline
Client → Servercall:accept{ paraId, signalData }Accept an incoming call
Server → Clientcall:accepted{ signalData, answererId }Notify caller the call was accepted
Client → Servercall:reject{ paraId }Reject an incoming call
Server → Clientcall:rejected{ fromId }Notify caller the call was rejected
Client → Servercall:signal{ paraId, signalData }Relay ICE candidates and re-negotiation
Server → Clientcall:signal{ fromId, signalData }Forwarded signal to the other peer
Client → Servercall:end{ paraId }Hang up an active call
Server → Clientcall:ended{ fromId }Notify the other peer the call ended
Client → Servercall:audio-chunk{ paraId, audioData, chunkIndex }Relay base64 audio for non-WebRTC fallback
Server → Clientcall:audio-chunk{ fromId, audioData, chunkIndex }Forwarded audio chunk to receiver
Client → Servercall:cancel_pending{ paraId }Cancel a stored pending call before receiver reconnects
Server → Bothcita:sugerenciaRestaurant + date suggestion objectEmitted automatically when a match reaches 5 messages

Automated Date Suggestion Logic

The cita:sugerencia event is emitted inside the mensaje:enviar handler — no client action is required. When the 5th message in a match is saved, the server:
  1. Queries active Restaurante documents (activo: true).
  2. Picks one at random and generates a suggested day/time (Friday, Saturday, or Sunday evening).
  3. Persists the recommendation inside the Match document under recomendacion.
  4. Emits cita:sugerencia to both users’ personal rooms with full restaurant details (name, category, atmosphere, address, cover photo, gallery, average price, hours, and up to 3 menu items).

File Storage (Cloudinary)

Binary assets are uploaded to Cloudinary through the backend — the mobile app never calls Cloudinary directly. Multer (src/middlewares/upload.middleware.js) handles multipart/form-data parsing before the controller streams the file to Cloudinary.
Asset typeRouteNotes
Profile picturePUT /api/users/profile-pictureSingle image, replaces previous
Cover photoPUT /api/users/cover-photoSingle image, replaces previous
Gallery photosPUT /api/users/galleryUp to 6 images per user
Chat imageVia POST /api/chat/upload or socket relaySent as multipart/form-data
Restaurant cover photoPOST /api/asociado/foto-portadaUsed in date suggestions
Restaurant galleryPOST /api/asociado/fotosMultiple photos for partner restaurants
The Express server is configured with a 20 MB body limit to accommodate multi-photo uploads sent as Base64 JSON when multipart is not suitable:
app.use(express.json({ limit: '20mb' }));
app.use(express.urlencoded({ extended: true, limit: '20mb' }));
The mobile API client uses a dedicated uploadClient Axios instance with a 90-second timeout for all file operations, separate from the 15-second timeout used for regular JSON requests.

Admin Panel

The admin panel is a vanilla HTML/CSS/JS single-page application located in the admin/ directory at the repository root. It is served directly by Express:
// src/app.js
const adminDir = path.join(__dirname, '../../admin');
app.use('/admin', express.static(adminDir));
app.get('/admin',   (_, res) => res.sendFile(path.join(adminDir, 'index.html')));
app.get('/admin/*', (_, res) => res.sendFile(path.join(adminDir, 'index.html')));
This means the admin panel is available at http://localhost:3000/admin with no separate server process. It authenticates against the same /api/login endpoint and stores its JWT locally, then uses the admin JWT role to access /api/admin/* routes gated by the soloAdmin middleware.
In production, set ALLOWED_ORIGINS to include the domain where the admin panel is hosted if it is served from a different origin than the API. If both are on the same domain (e.g. behind an Nginx reverse proxy), no CORS configuration is needed for the admin panel.

User Roles

Debuta supports three distinct roles, stored in the rol field of the Usuario MongoDB document:
RoleDescriptionGating middleware
userStandard app user — can swipe, chat, call, postverificarToken
adminFull platform administrator — manages users, reports, tickets, and restaurantesverificarToken + soloAdmin
asociadoRestaurant partner — manages their own restaurant profile and photos for date suggestionsverificarToken + soloAsociado
Role escalation is not possible through any public API endpoint; the rol field can only be set by an existing admin through the /api/admin routes.

CORS Configuration

CORS behaviour is determined by NODE_ENV:
  • developmentcors() with no restrictions; all origins are allowed. This makes it easy to connect the Expo dev server, ngrok tunnels, or localtunnel without any configuration changes.
  • production — Only origins listed in the ALLOWED_ORIGINS environment variable (comma-separated) are permitted. Both the Express CORS middleware and the Socket.io CORS config read this same variable, so a single env change covers the entire server.

Build docs developers (and LLMs) love