Más de 5.000 servicios en 6 plataformasHaz que tus redes despeguen
Panel SMM con precio calculado en servidor, saldo atómico y aviso de riesgo imposible de saltar
Seguidores · últimas 12 semanas
0
▲ 312 %Incluida la corrección natural del final: así se comporta una cuenta real.
En producción en https://zocialy-47c9c.web.app · compila limpio (typecheck + build) · pendiente saldo en Peakerr y pasarela automática
Plataforma web · Marketing en redes sociales (SMM) · Diseño de producto, arquitectura y desarrollo full-stack: landing, panel de cliente, panel de administración, API de reventa y app móvil de administración
Ocho barras que se levantan solas
Un panel de métricas contado como panel de métricas: cada columna es una cifra real del proyecto, en escala logarítmica para que la más grande no aplaste a las demás.
28
Pantallas (page.tsx)
36
Rutas de API en el servidor
16
Colecciones de Firestore
54
Componentes React
22
Ficheros de test con node:test
~40.000
Líneas de TypeScript en la web
11
Pantallas en la app Flutter de admin
6
Métodos de recarga configurados
Tres vicios del mercado y dos exigencias del contexto
Los paneles SMM del mercado comparten tres vicios: prometen crecimiento perpetuo escondiendo que las plataformas depuran cuentas y las métricas caen; confían el precio y el estado del pedido al navegador, que es exactamente donde no debe decidirse el dinero; y arrastran catálogos de miles de servicios que obligan a descargar la colección entera solo para rellenar un desplegable, con una factura de Firestore que crece sin que nadie la mire. A eso se sumaban dos exigencias del contexto venezolano: cobrar en bolívares con la tasa oficial del BCV, que no publica API y sirve la cifra dentro del HTML con una cadena de certificados incompleta, y verificar cada pago a mano sin que un doble clic acredite el saldo dos veces.
Se construyó un panel donde el navegador nunca escribe dinero ni estado. `/api/orders` ignora el importe que envía el cliente y lo recalcula desde `serviceId` + `quantity` con la misma función pura (`calculateCharge`) que usa la tarjeta de producto, el formulario y la API de reventa, así que lo que se muestra y lo que se cobra no pueden divergir. El cobro ocurre dentro de una transacción de Firestore y, si Peakerr rechaza el pedido, el importe vuelve al saldo con su movimiento trazado. La transparencia de riesgos se resolvió con una sola constante de texto (`RISK_NOTICE_TEXT`) que alimenta a la vez el formulario de compra, la página de política de refill y el resumen de la landing, con tres cerrojos encadenados: el hook no habilita el botón, el submit corta y la API devuelve 400. El coste se atacó sustituyendo listeners en vivo por páginas con cursor `startAfter`, agregaciones `count`/`sum` en servidor y un documento agregado `catalog/index` de 8 KB que reduce el formulario de pedido de más de 3.000 lecturas a una más la categoría elegida. Las recargas siguen un flujo manual verificado por un humano, con la tasa congelada en cada solicitud, aprobación idempotente y aviso a Telegram que jamás puede tumbar una solicitud ya guardada.
Aquí no se lee una advertencia: se choca contra ella
Un único texto constante alimenta compra, política y landing; el hook no habilita el botón sin aceptación, el submit corta y la API rechaza con 400. Cada pedido guarda `riskAcknowledgedAt` y `riskNoticeVersion` para dejar constancia de qué texto aceptó cada cliente.
Puerta de pago
Aviso importante: las métricas pueden fluctuar o descender tras la entrega. Las plataformas depuran cuentas y parte de lo entregado puede caer.
Marca la casilla para desbloquear el botón. Es la única llave.
Seis piezas donde se juega la confianza
El aviso de riesgo es una barrera, no un párrafo
Un único texto constante alimenta compra, política y landing; el hook no habilita el botón sin aceptación, el submit corta y la API rechaza con 400. Cada pedido guarda `riskAcknowledgedAt` y `riskNoticeVersion` para dejar constancia de qué texto aceptó cada cliente.
El dinero se decide en el servidor
El importe que envía el navegador se ignora y se recalcula desde servicio y cantidad; el saldo se descuenta en una transacción de Firestore, así que dos pestañas no gastan el mismo saldo dos veces, y un rechazo del proveedor dispara el reembolso automático con su movimiento trazado.
Firestore paginado con cursores y agregaciones
Nada de `onSnapshot` sobre listas ni de `offset`: páginas con `startAfter`, `limit(pageSize + 1)` para saber si hay siguiente sin una segunda consulta, y `getCountFromServer` / `getAggregateFromServer` para los resúmenes. El formulario de pedido bajó de más de 3.000 lecturas a una más la categoría elegida.
Claves de reventa con hash y consentimiento firmado
La API pública `/api/v2` imita el estándar de facto de los paneles SMM (POST form-urlencoded, siempre HTTP 200 con `error`). La clave solo vive como SHA-256 y se enseña una vez; como una API no tiene pantalla donde mostrar el aviso, el consentimiento se firma al generarla y cada pedido lo hereda.
El grupo de Telegram se conecta escribiendo `/id`
El enlace de invitación de un grupo privado no da `chat_id` y el modo privacidad oculta los mensajes normales al bot, pero los comandos siempre llegan. El webhook guarda el chat en `settings/site` con doble cerrojo: el secreto de cabecera de Telegram y el ID del administrador.
Tasa del BCV raspada con salvaguarda de rango
El BCV no tiene API y su cadena de certificados está incompleta, así que se usa el módulo `https` con la verificación relajada solo en esa petición. El parser lee `<div id="dolar">` en formato venezolano y rechaza cualquier cifra fuera de rango antes que cobrar mal; el refresco es perezoso y la tasa se congela en cada solicitud.
Un producto, tres puertas
Cliente, administración y teléfono comparten paleta, tipografía y reglas. Todo en tema claro forzado, todo en español, incluidas las rutas.
Panel del cliente — dashboard
Nueva orden — cascada y puerta de pago
Administración — resumen
Administración — métricas del sitio — sparklines dibujados a mano, sin librería de gráficas.
Zocialy admin
Recargas
Bs 3.284,00 · tasa congelada · ref. 004417832
USDT 50,00 · tasa congelada · ref. 8812-KQ
Toca para revisar el comprobante
$ 10,00 · tasa congelada · ref. ZN-4471
Toca para revisar el comprobante
App Flutter de administración — la misma paleta, para aprobar recargas desde el teléfono.
Las maquetas reproducen la interfaz real; los datos que muestran son de ejemplo.
Una Z que despega hasta un punto cian
Optimista y de producto: violeta profundo que abre a magenta y coral, como una flecha que despega. Neutros entintados de violeta para que ningún gris se vea sucio al lado de la marca, superficies blancas con sombras muy difusas de tinte morado, esquinas muy redondeadas (hasta 2rem) y un punto cian (#6bc5dc) usado con cuentagotas. Los semánticos (ámbar aviso, esmeralda correcto, rosa error) se dejan fuera de la marca a propósito: un aviso de riesgo no debe teñirse de violeta. Tema claro forzado con `color-scheme: light`.









Arrastra la tira · 8 piezas de marca, todas derivadas del mismo isotipo.
El navegador no escribe dinero
El importe que llega del cliente se descarta. La misma función pura que pintó el precio en pantalla lo recalcula en el servidor, y el saldo se mueve dentro de una transacción.
POST /api/orders
- serviceId: 1842
- quantity: 1000
- amount: 0.01
El importe se ignora.
calculateCharge(serviceId, quantity)
La misma función pura que usa la tarjeta de producto, el formulario y la API de reventa. Lo que se muestra y lo que se cobra no pueden divergir.
- users/{uid}.balance− 1,14
- orders/{id}creado
- transactions/{id}movimiento
Si Peakerr rechaza, el importe vuelve con su movimiento trazado.
Monorepo web en `/Users/macbook/zocialy` con el App Router de Next.js como única aplicación: `src/app/page.tsx` es la landing, `src/app/panel/*` el área de cliente (guarda de sesión), `src/app/admin/*` la de gestión (guarda `role=admin`) y `src/app/api/*` las 36 rutas de servidor. La lógica de negocio vive en `src/lib` en módulos puros que no importan el SDK a propósito —`pricing.ts`, `commissions.ts`, `referrals.ts`, `classify.ts`— para poder ejecutarse tal cual con `node --test`; los adaptadores con estado (`lib/firebase/admin.ts`, `lib/peakerr/client.ts`, `lib/bcv.ts`, `lib/trm.ts`) llevan `server-only`, de modo que el build falla si alguien intenta importar la clave del proveedor desde el navegador. Los datos se modelan en `lib/firebase/schema.ts` sobre 16 colecciones de Firestore, y las reglas declaran la frontera real: el cliente lee lo suyo y no escribe ni saldo ni estado de pedido. La lectura se hace siempre con `usePagedCollection` (cursores `startAfter` y `limit(pageSize + 1)`), y los resúmenes con agregaciones de servidor. Todo el despliegue va a Firebase Hosting con `frameworksBackend`, es decir, la app entera corre en Cloud Run sin una carpeta `functions/`. Aparte, `/Users/macbook/zocialy_admin` es una app Flutter independiente (Riverpod + go_router, ~7.300 líneas de Dart) que ataca las mismas colecciones y las mismas rutas de API con el token del administrador, reproduciendo píxel a píxel la paleta de la web para que se sientan un mismo producto.
Lista de entrega: 19 de 19
No es una lista de deseos: es lo que ya está en producción, contado como el panel cuenta un pedido completado.
Con qué está construido
Web
Backend y datos
App de administración
Integraciones
Calidad y operación
Nueve piedras y cómo se apartaron
El formulario de pedido necesitaba los más de 3.000 servicios del catálogo solo para rellenar tres desplegables, y cada visita se facturaba entera.
La sincronización escribe un documento agregado `catalog/index` de 8 KB con las redes y categorías, y los servicios se piden solo de la categoría que el usuario elige. La pantalla pasó de más de 3.000 lecturas a una más un lote de unos cientos, y solo cuando hace falta.
El flag `refill` de Peakerr no es fiable: cientos de servicios lo traen en `false` mientras anuncian «Refill 30d» en el nombre, y otros lo traen en `true` sin mencionarlo.
Se guardan las dos verdades en lugar de elegir una: `refill` es lo que el proveedor anuncia y es lo que ve el cliente; `refillViaApi` es si la acción automática funcionará. Cuando no hay automatismo la solicitud se registra igual y queda en cola manual, porque ocultar una garantía anunciada perjudica al cliente y prometer un botón que va a fallar, también.
El texto que devuelve el proveedor está escrito por humanos: emojis decorativos, caracteres matemáticos Unicode, banderas, la İ turca que NFKC no descompone y tres grafías distintas de «TikTok».
`lib/peakerr/classify.ts` normaliza con `cleanText` antes de reconocer nada, con casos explícitos para los caracteres que la normalización estándar no resuelve, y el resultado se verifica contra el catálogo real con scripts de comprobación en `scripts/`.
El BCV no publica API, sirve la tasa dentro del HTML en formato venezolano y su cadena de certificados está incompleta, con lo que Node falla con `UNABLE_TO_VERIFY_LEAF_SIGNATURE`.
Se usa el módulo `https` con la verificación desactivada únicamente en esa petición, en lugar de tocar `NODE_TLS_REJECT_UNAUTHORIZED`, que la relajaría también para Firebase y Peakerr. El parser lee la cifra de `<div id="dolar">`, la convierte desde el formato local y aplica una salvaguarda de rango: si un cambio de maquetación hiciera leer un año o un teléfono, se rechaza antes que cobrar mal.
Aprobar una recarga dos veces —un doble clic, dos pestañas— acreditaría el saldo dos veces, que es el error clásico de este tipo de cola.
La aprobación es una transacción idempotente: vuelve a leer el estado antes de acreditar, y acredita saldo, registra el movimiento y escribe la auditoría en el mismo bloque atómico. Se suman dos límites antifraude: máximo tres solicitudes pendientes por usuario y referencias de pago no reutilizables.
El aviso al canal de Telegram estaba en el camino del dinero: si Telegram fallaba, la solicitud de recarga se caía con él.
El módulo de avisos no lanza nunca. La solicitud se guarda primero y el aviso se intenta después; si Telegram no responde, el administrador la ve igual en el panel. El dinero del cliente no puede depender de la disponibilidad de un bot.
El menú del panel de cliente tiene nueve destinos y la barra inferior de móvil solo admite cuatro sin aplastar los textos, así que Historial, Afiliados, API, Garantía y Soporte quedaban inalcanzables desde el teléfono, que es por donde entra la mayoría.
Los cuatro destinos fijos se seleccionan por `href` y no por posición (antes un `slice(0, 4)` expulsaba «Saldo» al insertar una entrada nueva), y un quinto botón abre el resto en una hoja, el patrón que usan las apps nativas. El botón «Más» se marca activo cuando la ruta actual está dentro de la hoja.
`firebase-admin` es CommonJS y al empaquetarlo Next rompía la interoperabilidad: `AggregateField` llegaba como `undefined` y cualquier consulta con `sum()` reventaba solo en producción.
Se declaró en `serverExternalPackages` para que se cargue en tiempo de ejecución tal cual lo publica el paquete. El fallo era invisible en local, que es la peor forma de encontrarlo.
Cada despliegue dejaba una imagen de contenedor nueva en Artifact Registry; en dos días de trabajo se acumularon 5,5 GB en 22 imágenes, el único coste real del proyecto.
Una política de limpieza documentada en `infra/artifact-cleanup.json`: nunca borrar una imagen etiquetada (Cloud Run sirve por etiqueta), conservar las tres más recientes y borrar las huérfanas. La imagen viva queda protegida dos veces.
Zocialy es un panel de servicios de marketing en redes sociales (seguidores, me gusta, visualizaciones) construido sobre Next.js 15 y Firebase, con el catálogo del proveedor mayorista Peakerr como fuente de servicios.
Son tres productos en un mismo repositorio: una landing pública animada sin librerías de motion, un panel de cliente con 9 secciones y un área de administración con 12, más una app Flutter aparte para aprobar recargas desde el teléfono.
La frontera de seguridad no está en el navegador: el precio se recalcula siempre en el servidor a partir de servicio y cantidad, el saldo se descuenta dentro de una transacción de Firestore y los roles viven en custom claims, no en un campo del documento que el usuario pueda escribir.
El requisito más delicado del cliente —advertir de la caída natural de métricas tras la entrega— está implementado como una barrera real: un único texto constante, un componente que siempre se renderiza antes del botón de pago y un rechazo 400 en la API si el pedido llega sin la aceptación firmada.
El proyecto está escrito íntegramente en español, incluidos rutas, nombres de estado en Firestore y comentarios de código, y toda la interfaz se sirve en tema claro forzado.
Zocialy
¿Quieres algo así para tu producto?
Un panel donde el precio se decide en el servidor, el saldo se mueve en una transacción y la advertencia al cliente es una regla del sistema, no un párrafo decorativo. Si necesitas producto, pasarela y panel de gestión en el mismo repositorio, es exactamente este terreno.