Corto, inequívocamente colombiano, y el sufijo -azo convierte una rifa cualquiera en un evento. Fácil de dictar por teléfono, fácil de escribir en WhatsApp.
Boleta troquelada: perforación al centro, muescas laterales, la balota en oro a la izquierda y los renglones del talonario a la derecha. Legible a 16 px como favicon.
Los grises llevan sesgo azul hacia el acento. Verde y rojo son solo semánticos: nunca decoran. El oro marca lo que vale — el premio y el número apartado.
Todo número va en monoespaciada con tabular-nums. En una grilla de 100 boletas, dígitos de ancho variable rompen la retícula.
Este prototipo usa los nombres de clase de Bootstrap 5 (row, col-md-6, form-control, card, badge, table). El marcado se traslada tal cual al proyecto PHP enlazando bootstrap.min.css más una hoja rifazo.css con estos tokens de color.
Apartas tu número, transfieres por Bre-B y quedas participando. Confirmación por correo, y el comprobante lo mandas por WhatsApp si prefieres.
Déjanos tu correo y te avisamos cuando abra el siguiente.
1. Escoges tus números y los apartas. 2. Transfieres por Bre-B con la referencia que te damos. 3. Subes el comprobante o lo mandas por WhatsApp. 4. El organizador confirma y tus números quedan tuyos. Tienes 24 horas para pagar; a las 20 te recordamos.
Dudas, devoluciones o envío de comprobante: 300 481 2277 — abrir WhatsApp
Al apartar, tus números quedan bloqueados 24 horas mientras completas el pago.
Necesitamos cómo contactarte si ganas. Nada más.
No pedimos dirección. Solo se le solicita al ganador, y solo si el premio requiere envío. Menos datos recolectados es menos riesgo bajo la Ley 1581.
@rifazo
RF01-8802
Escanea el QR o usa la llave, y escribe la referencia en la descripción de la transferencia. Es lo que nos deja emparejar tu pago con tus números sin llamarte.
Captura o PDF, máximo 5 MB. Queda registrado al instante.
Se abre WhatsApp con el mensaje y la referencia ya escritos. Solo adjuntas la captura y envías.
Un comprobante puede ser falso o reutilizado. El panel compara monto, referencia y el hash del archivo para avisarte si algo se repite, pero la confirmación final la haces tú a ojo contra tu app del banco. Con 50 sorteos en paralelo esto se vuelve el cuello de botella.
Tienes 24 horas desde que apartaste. A las 20 horas te llega un recordatorio por correo y WhatsApp. Si a las 24 no hay pago confirmado, los números vuelven a quedar libres.
Tus números 07 · 42 · 88 quedan apartados a tu nombre mientras el organizador valida el pago. Tiene 24 horas para hacerlo, normalmente responde el mismo día.
Te escribimos en cuanto el pago quede confirmado.
Escribe tu celular y te enviamos un código de 6 dígitos. No creamos cuentas ni contraseñas para compradores: menos fricción y menos datos que proteger.
| Sorteo | Números | Referencia | Estado | Vence | |
|---|---|---|---|---|---|
| Anillo en oro 18 k | 07 · 42 · 88 | RF01-8802 | Verificando | 28 ago 10:42 | |
| Bono Éxito $500.000 | 47 | RF00-2210 | ¡Ganaste! | — | |
| Audífonos Sony | 15 · 16 | RF00-7734 | Reserva vencida | 14 ago 09:10 |
Acceso restringido al organizador
2FA obligatorio para todo rol administrativo. El módulo que confirma pagos mueve dinero: no puede depender solo de una contraseña.
A este ritmo el sorteo del anillo se agota el 2 de septiembre, dos días antes del cierre. Faltan 38 boletas por $1.140.000.
3 reservas se liberan hoy sin comprobante recibido. Los números vuelven a la grilla automáticamente.
RF01-8811 tiene el mismo archivo que RF01-8802. Revísalo antes de confirmar.
Enlaces wa.me activos. La Cloud API sigue sin configurar en Ajustes.
Última corrida del liberador: hace 47 segundos. Todo al día.
| Sorteo | Rango | Vendidas | Lotería | Juega | Estado | |
|---|---|---|---|---|---|---|
| Anillo en oro 18 k RF-2026-001 |
00 – 99 | 62 | Bogotá | 05 sep | Activo | |
| Audífonos Sony WH-1000 RF-2026-000 |
00 – 99 | 100 | Medellín | 15 ago | Finalizado | |
| Cafetera Oster RF-2026-002 |
00 – 99 | 0 | Valle | 19 sep | Borrador |
Con boletas vendidas no se puede reducir el rango ni cambiar el valor de la boleta ni las cifras a comparar. Cambiar cualquiera de los tres rompe el contrato con quien ya compró. El sistema los bloquea con la primera venta.
El rango y las cifras a comparar tienen que cuadrar: 2 cifras exige 00–99. Si eliges 3 cifras con 100 boletas, el formulario no guarda.
7 reservas esperando · la más urgente vence en 1 h 12 min
| Referencia | Comprador | Números | Monto | Comprobante | Vence en | Acción |
|---|---|---|---|---|---|---|
| RF01-8802 hace 12 min |
Laura R. •••• 2277 |
07 42 88 | $90.000 | Subido | 23 h 48 m | |
| RF01-8809 hace 22 h |
Andrés M. •••• 6610 |
31 | $30.000 | Por WhatsApp | 1 h 12 m | |
| RF01-8811 hace 1 h |
Diego S. •••• 0193 |
55 56 | $30.000 (esp. $60.000) | Archivo repetido | 22 h 59 m | |
| RF01-8795 hace 25 h |
Sandra P. •••• 4412 |
19 | — | Sin comprobante | Liberada |
RF01-8811: el monto no cuadra con los números apartados y el archivo tiene el mismo hash que RF01-8802. El sistema no deja confirmar sin que registres el motivo.
Los números pasan de reservado a vendido dentro de una transacción, sin vuelta atrás, y sale el correo de confirmación al comprador.
El cron de cPanel corre cada minuto: libera los números, marca la reserva como vencida y deja el registro. Nada se borra.
Se muestran al comprador en el orden que definas
Enviar y adjuntar captura. Respaldo por si el comprador no usa Bre-B.
Pasarela con confirmación automática por webhook: tarjeta, PSE y Nequi. La interfaz ya existe; solo faltan las llaves.
Cuando llegues a varios sorteos en paralelo, esto deja de ser opcional.
| Nombre | Tipo | Destino | Orden | Estado | |
|---|---|---|---|---|---|
| Bre-B | Manual · QR + llave | @rifazo | 1 | Activa | |
| Nequi | Manual | 300 481 2277 | 2 | Activa | |
| Daviplata | Manual | 311 902 4415 | 3 | Inactiva | |
| Bancolombia | Manual | •••• 4471 | 4 | Inactiva | |
| Wompi | Pasarela | — | 5 | Sin configurar |
Las formas de pago son datos, no código. Agregar una es insertar una fila con su tipo, destino, instrucciones y QR: el flujo del comprador no cambia.
Solo el equipo organizador. Los compradores no tienen cuenta.
| Usuario | Rol | 2FA | Último ingreso | Estado | |
|---|---|---|---|---|---|
| Omar P. o•••@rifazo.co | Superadmin | Activo | Hoy 08:14 | Activo | |
| Carolina M. c•••@rifazo.co | Tesorería | Activo | Hoy 07:52 | Activo | |
| Julián R. j•••@rifazo.co | Operación | Pendiente | 25 ago 16:30 | Bloqueado | |
| Marcela T. m•••@rifazo.co | Solo lectura | Activo | 12 jul 09:05 | Inactivo |
Los usuarios se inactivan, nunca se borran: sus acciones en la bitácora deben seguir siendo atribuibles. Y un superadmin no puede inactivarse a sí mismo ni quedar como el único activo.
Todo parametrizable desde aquí, sin tocar código. Hoy el MVP funciona con enlaces wa.me; la Cloud API se activa cuando tengas la verificación de Meta.
Con la Cloud API, los mensajes de marketing exigen plantilla aprobada y consentimiento verificable por número. Mandar promoción en frío a una lista es la vía rápida a que te suspendan el número. Para captar nuevos compradores el canal es el botón de compartir, no el envío masivo.
Cero trámite, cero costo por mensaje y funciona hoy. Lo que pierdes es el envío automático: el comprador toca un botón en lugar de recibir el mensaje solo. Las notificaciones automáticas del MVP salen por correo.
Un solo catálogo de disparadores. Cada uno decide si sale por correo, por WhatsApp o por los dos.
| Plantilla | Cuándo se dispara | Correo | Estado | ||
|---|---|---|---|---|---|
| Sorteo activado sorteo_nuevo | Al publicar un sorteo | On | Compartir | Activa | |
| Reserva creada reserva_creada | Al apartar números | On | Enlace | Activa | |
| Recordatorio a las 20 h reserva_recordatorio | 80% del plazo sin pago | On | Enlace | Activa | |
| Reserva vencida reserva_vencida | Al liberar el número | On | — | Activa | |
| Pago confirmado pago_confirmado | Al validar en el panel | On | Enlace | Activa | |
| Pago rechazado pago_rechazado | Al rechazar un comprobante | On | — | Activa | |
| Últimas boletas sorteo_por_cerrar | Manual · campaña | On | Compartir | Solo correo | |
| Resultado del sorteo resultado_sorteo | Al registrar el acta | On | Enlace | Activa |
Enlace = el correo y la pantalla incluyen un botón wa.me. Compartir = botón para que el comprador reenvíe el sorteo a sus contactos. Cuando actives la Cloud API estas pasan a On con plantilla aprobada.
Cada notificación entra a una tabla notif_queue y el cron de cPanel la despacha cada minuto con reintentos. Si tu hosting limita correos por hora, la cola respeta el tope en lugar de perder mensajes.
Ajustado a lo que tu hosting con cPanel realmente soporta, y a las tecnologías que mantienes tú.
| Capa | Elección | Por qué |
|---|---|---|
| Servidor | PHP 8.2+ · Apache | Lo que trae cPanel. Sin Composer ni dependencias externas obligatorias. |
| Estructura | Front controller + MVC ligero | Un index.php enruta, controladores en /app, vistas en /views. Nada mágico que no puedas leer. |
| Base de datos | MySQL 8 / MariaDB · PDO | Consultas preparadas siempre. Se crea desde el asistente de cPanel. |
| Frontend | Bootstrap 5.3 + JS vanilla | Bootstrap local (no CDN) más rifazo.css con los tokens de este prototipo. |
| Tareas programadas | Cron de cPanel · cada minuto | php cron/tick.php libera reservas vencidas y despacha la cola de correos. |
| Correo | SMTP autenticado del dominio | Cuenta del cPanel con SPF y DKIM. Sin mail() a pelo, que cae en spam. |
| wa.me · Cloud API opcional | Enlaces hoy; credenciales de Meta parametrizables en el panel. | |
| Archivos | Fuera de public_html | Comprobantes en /storage con .htaccess denegando todo, servidos por un PHP que valida sesión. |
| TLS | AutoSSL de cPanel | Let's Encrypt automático, con redirección forzada a HTTPS. |
Sin framework, cosas que Laravel te daría gratis las escribo a mano: enrutado, CSRF, sesiones seguras, migraciones y plantillas. Es perfectamente mantenible y tú lo entiendes, pero la seguridad depende de que ese código esté bien hecho, no de una librería probada por miles. Lo compenso con un archivo único de helpers de seguridad que toda página incluye de entrada.
/home/usuario/
├── rifazo/ ← fuera de public_html, no accesible por web
│ ├── app/ Controladores y modelos
│ ├── views/ Plantillas PHP
│ ├── lib/ db.php · security.php · mailer.php · wa.php
│ ├── storage/comprobantes/ Subidas (con .htaccess Deny from all)
│ ├── cron/tick.php Corre cada minuto
│ └── config.php Credenciales · CHMOD 600, fuera de la web
└── public_html/
├── index.php Front controller (require ../rifazo/app)
├── admin.php Front controller del panel
├── assets/ bootstrap.min.css · rifazo.css · app.js
└── .htaccess HTTPS forzado, cabeceras, sin listado
Con reservas de 24 horas y confirmación humana, la única forma de no vender dos veces el mismo número es que MySQL lo impida, no el PHP.
-- Un número por sorteo, garantizado por el motor
CREATE TABLE boleta (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
sorteo_id INT UNSIGNED NOT NULL,
numero SMALLINT UNSIGNED NOT NULL,
estado ENUM('libre','reservado','vendido') NOT NULL DEFAULT 'libre',
pedido_id INT UNSIGNED NULL,
reservado_hasta DATETIME NULL,
UNIQUE KEY uq_sorteo_numero (sorteo_id, numero),
KEY ix_expira (estado, reservado_hasta),
CONSTRAINT fk_b_sorteo FOREIGN KEY (sorteo_id) REFERENCES sorteo(id)
) ENGINE=InnoDB;
-- Apartar: transacción con bloqueo de fila
$db->beginTransaction();
$st = $db->prepare(
"SELECT id FROM boleta
WHERE sorteo_id = ? AND numero IN (?,?,?)
AND (estado = 'libre'
OR (estado = 'reservado' AND reservado_hasta < NOW()))
ORDER BY numero
FOR UPDATE"); // ← serializa a los compradores simultáneos
// si devuelve menos filas de las pedidas → rollback y avisar "ya lo tomaron"
$db->prepare("UPDATE boleta SET estado='reservado', pedido_id=?,
reservado_hasta = NOW() + INTERVAL 24 HOUR
WHERE id IN (...)")->execute();
$db->commit();
UNIQUE(sorteo_id, numero) hace imposible duplicar un número aunque falle todo lo demás. FOR UPDATE serializa a dos compradores que tocan el mismo número en el mismo segundo, y ORDER BY numero evita interbloqueos entre pedidos que se cruzan. La reserva vencida se recicla en la misma consulta: si el cron se cae, la venta no se detiene.
usuario id, email, pass_hash(bcrypt), rol, totp_secret_enc, activo, creado_en
sorteo id, codigo, nombre, descripcion, condiciones, inicio, fin,
num_desde, num_hasta, valor_boleta, horas_reserva,
loteria, fecha_loteria, cifras, regla_no_vendida,
estado, creado_por
sorteo_foto id, sorteo_id, archivo, orden
boleta id, sorteo_id, numero, estado, pedido_id, reservado_hasta ← tabla caliente
comprador id, cel_hash, cel_enc, nombre_enc, email_enc, depto, ciudad,
consent_en, consent_ip, consent_version
pedido id, codigo, sorteo_id, comprador_id, medio_pago_id, monto,
estado(pendiente|confirmado|rechazado|vencido),
comprobante, comprobante_sha256, canal_comprobante(web|whatsapp),
validado_por, validado_en, motivo_rechazo, reservado_hasta
medio_pago id, nombre, tipo(manual|pasarela), destino, qr_archivo,
instrucciones, activo, orden
plantilla id, clave, canal(email|whatsapp), asunto, cuerpo, activa
notif_queue id, plantilla_id, pedido_id, destino_hash, intentos,
estado, error, programado_para, enviado_en
bitacora id, usuario_id, accion, entidad, entidad_id,
antes_json, despues_json, ip, ua, creado_en
ajuste clave, valor_enc ← credenciales de Meta, SMTP
departamento codigo, nombre ciudad codigo, depto_codigo, nombre
Los campos _enc van cifrados con openssl_encrypt AES-256-GCM y la llave en config.php fuera de la web. cel_hash permite buscar por celular sin descifrar la tabla entera.
estado='libre', pedido a vencido, registro en bitácora.notif_queue respetando el límite por hora del hosting, con reintento exponencial.# Cron Jobs de cPanel · cada minuto
* * * * * /usr/local/bin/php /home/usuario/rifazo/cron/tick.php >> /home/usuario/rifazo/cron/tick.log 2>&1
El cron tiene que ser reentrante: si una corrida se demora más de un minuto, la siguiente arranca encima. Se resuelve con un lock por archivo (flock) al inicio de tick.php. Sin eso, un correo puede salir dos veces.
password_hash con bcrypt y TOTP obligatorio en el panelhttponly, secure, samesite=Lax y regeneración al ingresarhtmlspecialchars en todas las vistasX-Content-Type-Options, Referrer-Policy en .htaccessnoindex mientras no haya permisosLo que quedó cerrado con tus aclaraciones y lo que sigue abierto.
| Tema | Definición | Estado |
|---|---|---|
| Medio de pago | Bre-B con QR y llave como principal; Nequi de respaldo. Todo manual. | Cerrado |
| Comprobante | Dos rutas: subida en el checkout o envío por WhatsApp al organizador. | Cerrado |
| Reserva | 24 h de bloqueo · recordatorio automático a las 20 h · liberación a las 24 h. Va en las condiciones de todo sorteo. | Cerrado |
| Ganador | Lotería nacional elegida al crear el sorteo, con fecha y cifras a comparar. | Cerrado |
Enlaces wa.me en el MVP; credenciales de Cloud API parametrizables en el panel. | Cerrado | |
| Stack y hosting | PHP puro + MySQL + Bootstrap sobre cPanel de BanaHosting. | Cerrado |
| Devoluciones | Por WhatsApp con el organizador. Falta la contraparte en el sistema: anular un pedido con motivo y liberar los números deja rastro; sin eso el dinero devuelto no queda registrado en ninguna parte. | Parcial |
| Permisos de rifa | Sin autorización en el piloto, por decisión tuya. Vender boletas con dinero real sigue siendo una rifa aunque sea entre conocidos: no hay exención por grupo cerrado. | Riesgo asumido |
| Dominio y correo | Falta definir el dominio para configurar SPF, DKIM y el remitente de los correos. | Abierto |
50 sorteos en paralelo de 100 boletas son 5.000 validaciones manuales, cada una con ventana de 24 h y comprobantes entrando por dos canales. El cuello de botella no va a ser el software: va a ser la persona revisando. Para el anillo vas bien; a partir del tercer o cuarto sorteo simultáneo la pasarela deja de ser opcional.
Puedo construir todo sin esperar la autorización, y el piloto queda marcado como privado por invitación con noindex. Antes de abrirlo al público hay que sacar los permisos.