Sistema automatizado para obtener resultados del Quini 6, procesarlos, validar tickets de usuarios y notificar si ganaron.
El proyecto utiliza Node.js (Express + node-cron) para el backend y la automatización, PostgreSQL para almacenar datos y Telegram como canal principal de comunicación con los usuarios.
Automatizar el proceso de:
- Obtener el resultado del último sorteo del Quini 6
- Extraer números ganadores y premios
- Guardar resultados en base de datos
- Comparar los tickets de usuarios contra el sorteo
- Detectar si un ticket ganó
- Notificar al usuario automáticamente
Backend (Node.js + Express)
│
├── Cron (node-cron): mié/dom 21:20–21:55 cada 5 min → fetch + parse + guardar + validar + notificar
├── Cron 22:00 → alerta al admin si no se obtuvo el resultado
├── Cron mar/sáb 20:00 → recordatorio "mañana es sorteo"
│
▼
PostgreSQL
│
▼
Bot de Telegram (comandos + notificaciones)
Los resultados se obtienen desde: https://www.quini-6-resultados.com.ar/
Desde esa página se extrae:
- Fecha del sorteo
- Número de sorteo
- Números ganadores
- Detalle de ganadores
- Premios por división
- Premio por ganador
- Pozo acumulado
Cada sorteo tiene varias modalidades.
Se gana con:
- 4 aciertos
- 5 aciertos
- 6 aciertos
Se gana con:
- 4 aciertos
- 5 aciertos
- 6 aciertos
Se gana únicamente con:
- 6 aciertos
Se gana con:
- 5 aciertos
El Pozo Extra se calcula usando los números de:
- Tradicional
- Segunda
- Revancha
Se crea la unión de todos los números sin repetir.
Un ticket gana el Pozo Extra si sus 6 números están contenidos dentro de esa unión.
Si un ticket ya ganó en:
- Tradicional
- Segunda
- Revancha
- Siempre Sale
entonces NO participa del Pozo Extra.
Cuando el cron obtiene un resultado nuevo, se hace lo siguiente:
- Se validan solo los tickets que ya existían el día del sorteo:
created_atdel ticket (fecha de alta) debe ser ≤ fecha del sorteo. Los tickets agregados después de esa fecha no participan de ese sorteo. - Tipo de ticket:
- Único: participa solo en un sorteo (el próximo desde que se dio de alta). Una vez que tiene un resultado guardado, ya no se valida en sorteos siguientes.
- Fijo: participa en todos los sorteos desde su alta; se genera automáticamente un resultado (
ticket_results) por cada sorteo.
- Para esos tickets se calcula el resultado contra ese sorteo (Tradicional, La Segunda, Revancha, Siempre Sale, Pozo Extra).
- Se envía un mensaje por usuario (por Telegram) con sus resultados personales:
- Resumen del sorteo (números ganadores y premios por modalidad).
- Por cada ticket del usuario: Alta (fecha en que se dio de alta el ticket, DD/MM/YY), números y desglose por modalidad.
- Tradicional y La Segunda: se muestran los aciertos (0–6) y si ganó (4, 5 o 6 aciertos).
- Revancha: aciertos y si ganó (solo con 6).
- Siempre Sale: aciertos y si ganó (solo con 5).
- Pozo Extra: si participó (no ganó en otra), cuántos números en la unión y si ganó (6 en la unión); si no participó se indica “No participa (ganaste en otra modalidad)”.
Cada usuario recibe un solo mensaje con todos sus tickets y el detalle de aciertos/ganador por modalidad. Quien ganó en alguna modalidad lo ve marcado con premio en ese mismo mensaje. La fecha de alta del ticket (created_at en DB) se muestra en el mensaje y define para qué sorteos participa ese ticket.
El parser del HTML genera una estructura como esta:
{
"drawDate": "2026-03-11",
"drawDateRaw": "11/03/2026",
"contestNumber": "3355",
"jackpot": "$4.400.000.000",
"jackpotAmount": 4400000000,
"modalities": {
"tradicional": {
"numbers": ["09","11","12","14","18","20"],
"prizes": [
{
"hits": "6",
"winners": 2,
"prize": "$390000000",
"prizeAmount": 390000000,
"prizePerWinner": "$195000000"
}
]
}
}
}Usuarios registrados en el sistema.
id name telegram_chat_id telegram_username is_active reminder_enabled created_at updated_at
Cada usuario puede tener múltiples tickets. created_at es la fecha de alta del ticket; solo se valida contra sorteos cuya fecha sea ≥ esa fecha (un ticket agregado después de un sorteo no participa de ese sorteo).
- tipo (
'unico'|'fijo'):- Único: el ticket participa solo en el próximo sorteo (el primero desde que se dio de alta). Una vez que tiene un
ticket_result, no se vuelve a validar. - Fijo: el ticket participa en todos los sorteos desde su alta; se crea automáticamente un
ticket_resultpor cada sorteo.
- Único: el ticket participa solo en el próximo sorteo (el primero desde que se dio de alta). Una vez que tiene un
id user_id label numbers_json tipo is_active created_at updated_at
["09","11","12","14","18","20"]
Resultados de cada sorteo.
id contest_number draw_date result_json created_at
Resultado de cada ticket contra cada sorteo.
id ticket_id contest_number draw_date won_any_prize results_json created_at
El backend usa node-cron para ejecutar el ciclo completo en miércoles y domingos:
- 21:20 a 21:55 (cada 5 min): intenta obtener el resultado del sitio, parsea el HTML, guarda en DB (solo si el resultado tiene datos completos), valida todos los tickets activos y notifica por Telegram a los ganadores. Si el sorteo ya estaba guardado, no hace nada.
- 22:00: si hasta ese momento no se pudo obtener el resultado del día, envía una alerta al admin por Telegram.
- Martes y sábado 20:00: envía recordatorio a los usuarios que lo tienen activado: "Mañana es sorteo a las 21:15 hs".
Cada ticket se compara contra: Tradicional, La Segunda, Revancha, Siempre Sale y Pozo Extra.
Ejecución manual (admin o API): POST /api/run-cycle o comando /runcycle en Telegram.
Telegram es la interfaz para los usuarios. Para registrarse hace falta un código de invitación (/start CODIGO).
Comandos de usuario
| Comando | Descripción |
|---|---|
/start CODIGO |
Registrarse con el código de invitación |
/add 09,11,12,14,18,20 [unico|fijo] |
Agregar un ticket. Opcional: unico (solo próximo sorteo) o fijo (todos; por defecto). Límite configurable. |
/tickets |
Ver mis tickets |
/delete 3 |
Eliminar el ticket N° 3 |
/resultado o /check |
¿Cómo me fue en el último sorteo? |
/ultimo |
Ver el último sorteo guardado con premios |
/historial |
Lista de los últimos sorteos guardados |
/sorteo 11/03/2026 o /sorteo 3355 |
Buscar un sorteo por fecha o número |
/recordar |
Activar/desactivar recordatorio antes del sorteo |
/premios |
Explicación del Quini 6 y cómo funcionan los premios (modalidades, pozo extra) |
/help |
Ayuda |
Comandos admin (solo si está configurado ADMIN_TELEGRAM_ID). Si sos admin, al hacer /start o /help el bot te indica que sos admin y te muestra estos comandos:
| Comando | Descripción |
|---|---|
/runcycle |
Forzar ciclo completo (fetch + validar + notificar) |
/status |
Estado del sistema (usuarios, tickets, último sorteo) |
/testresult |
Vista previa del mensaje de resultados (tus tickets contra el último sorteo) |
/resetdb |
Reiniciar toda la data de la base (usuarios, tickets, resultados de sorteos y validaciones) |
/broadcast mensaje |
Enviar un mensaje a todos los usuarios |
Cron (miércoles y domingo, 21:20–21:55 cada 5 min) │ Fetch Quini Results → Parse HTML → Save Draw Result │ Load Active Tickets → Check Each Ticket → Save Ticket Results │ Notify Winners (Telegram) │ 22:00 → Si no se obtuvo resultado: alerta al admin
El proyecto está preparado para desplegarse en Railway con deploy automático desde este repositorio.
- Cuenta en Railway (plan con PostgreSQL).
- Repositorio en GitHub/GitLab conectado a Railway.
-
Crear un proyecto en Railway y conectar este repositorio (Deploy from GitHub repo).
-
Añadir PostgreSQL al proyecto: en el dashboard, New → Database → PostgreSQL.
-
Enlazar PostgreSQL al backend (importante): el backend necesita
DATABASE_URL. En el servicio de tu app (backend):- Entrá a Variables (o Settings → Variables).
- Clic en "+ New Variable" o "Add Reference" / "Reference Variable".
- Elegí referenciar el servicio PostgreSQL y la variable
DATABASE_URL. Así Railway inyecta la URL real de la base (no localhost). - Si no usás referencias: en el servicio PostgreSQL, en Connect o Variables, copiá el valor de
DATABASE_URLy crealo como variable en el servicio del backend.
-
Configurar el servicio del backend:
- Root Directory: dejar vacío (el build usa la raíz).
- Builder: Railway detecta el
Dockerfileen la raíz del repo y construye con Docker. Elrailway.tomlopcional refuerza el uso de Dockerfile. - Variables de entorno (Settings → Variables):
Variable Descripción DATABASE_URLInyectada por Railway al enlazar PostgreSQL (no hace falta crearla a mano). TELEGRAM_BOT_TOKENToken del bot de Telegram (obligatorio para el bot). TELEGRAM_WEBHOOK_URL(Opcional en Railway) Si no la definís, en producción se usa https://<RAILWAY_PUBLIC_DOMAIN>/telegram-webhookautomáticamente. Asegurate de que el servicio tenga dominio público generado en Railway. Evita el error 409.ADMIN_TELEGRAM_IDID de Telegram del admin (comandos /runcycle,/status,/resetdb,/broadcast; en/starty/helpse muestra que sos admin y la lista de comandos admin).INVITE_CODECódigo para registrarse con /start CODIGO.MAX_TICKETS_PER_USER(Opcional) Límite de tickets por usuario; por defecto 10. LOG_LEVEL(Opcional) Nivel de log: trace,debug,info,warn,error; por defectoinfo.API_SECRET(Recomendado en producción) Clave para proteger todas las rutas /api/*. Sin ella, la API es pública. Header:Authorization: Bearer <API_SECRET>oX-API-Key: <API_SECRET>./healthy/telegram-webhooksiguen siendo públicas.PORTLo asigna Railway; no suele hacer falta definirlo. -
Schema de la base de datos: en el primer arranque, si la tabla
usersno existe, el backend ejecuta automáticamente el contenido debackend/src/db/schema.sql. No hace falta correr migraciones a mano. -
Deploy: cada push a la rama conectada (por ejemplo
main) dispara un nuevo build y deploy. Los logs se ven en el panel de Railway.
railway.toml: (opcional) builder Dockerfile, política de reinicio.Dockerfile: en la raíz; construye desdebackend/e inicia connode src/index.js. Es el que Railway/Nixpacks detectan por defecto.
- Node.js (Express) — API, parser HTML y lógica de validación
- node-cron — automatización (sorteos y recordatorios)
- PostgreSQL — almacenamiento de resultados, usuarios y tickets
- Telegram Bot API — comunicación con usuarios
- Pino — logging estructurado (JSON en producción, salida legible en desarrollo)