1. Arquitectura del sistema
Stack tecnológico
FrontendHTML/CSS/JS vanilla (sin framework)
HostingHostinger LiteSpeed — app.festivera.com
Base de datosSupabase (PostgreSQL) — gpswnzcfyhxzldijgewa.supabase.co
API backendPHP 8.x en Hostinger (carpeta api/)
Pagos COBold (webhook en api/bold-webhook.php)
EmailResend API (transaccional + marketing)
FacturaciónDIAN UBL 2.1 — XAdES-B-B, PHP (api/billing/dian/)
Flujo de datos principal
# Compra de boleta
Usuario → frontend (dashboard.html)
→ Bold (pago)
→ bold-webhook.php (confirmación)
→ Supabase orders + tickets + cashless_wallets (INSERT)
→ Resend API (email confirmación con QR)
# Acceso en portería
Portero → validador.html (escanea QR)
→ Supabase tickets (UPDATE estado = 'usado')
→ Respuesta visual verde/rojo
# Consumo cashless
Cajero → cajero.html (tap pulsera NFC)
→ Supabase cashless_wallets (deducción saldo)
→ cashless_transactions (INSERT log)
2. Catálogo de hardware
| Dispositivo | Función | Cantidad recomendada |
| Lector NFC de escritorio USB | Punto de venta / cajero | 1 por punto de venta |
| Tablet Android (10") | Panel cajero, validador, guardarropa | 1 por operador |
| Smartphone Android | Recargas NFC portátiles | 1 por recargador |
| Chip NFC Festivera (pulsera) | Wallet cashless del asistente | 1 por asistente |
| Router WiFi 5GHz | Red del evento | 1 por cada 150 personas |
| Switch PoE | Red cableada para puntos fijos | 1 por zona |
| Impresora térmica QR | Boletas físicas in-situ | Opcional (1 por ingreso) |
Los chips NFC Festivera son de uso exclusivo de la plataforma. Siempre referirse a ellos como "Chip NFC Festivera" o "Chip NFC certificado". Nunca mencionar al público las especificaciones técnicas del chip.
3. Configuración NFC y pulseras
Escritura de pulseras (producción masiva)
- 1Conecta el lector NFC USB al equipo de producción.
- 2Abre app.festivera.com/recargar.html (panel de recargas) con las credenciales de operador.
- 3Selecciona el evento correspondiente en el selector de evento.
- 4El sistema lee el UID del chip al acercar la pulsera al lector. Confirma el UID en pantalla antes de asignar.
- 5Haz clic en "Asociar pulsera" — el sistema crea un registro en
cashless_wallets con saldo inicial $0.
- 6Repite para cada pulsera del evento.
Para eventos masivos (+500 pulseras), exporta el lote de UIDs desde el lector y usa el endpoint api/bulk-register-wallets.php con el CSV. Consulta la documentación de la API interna.
Lectura NFC en cajero / validador
Los paneles de cajero y validador usan Web NFC API (Chrome Android) con fallback a BarcodeDetector + jsQR para dispositivos sin NFC o usando QR. El estándar es:
- 1BarcodeDetector (cámara) como método primario de QR.
- 2jsQR como fallback si BarcodeDetector no está disponible.
- 3NFC (Web NFC API) para pulseras físicas.
- 4Entrada manual de código como último recurso.
Compatibilidad NFC
| Dispositivo | Web NFC | Alternativa |
| Android Chrome 89+ | ✅ Nativo | — |
| iPhone / Safari | ❌ No soportado | QR con cámara |
| Chrome desktop + lector USB | ❌ No Web NFC | Lector envía como teclado |
| Firefox Android | ❌ | QR con cámara |
4. Requisitos de red en el evento
Topología recomendada
Internet (4G/Fibra backup)
│
├── Router principal (WiFi 5GHz)
│ ├── Red "FestiveraOPS" (operadores, protegida)
│ │ ├── Tablets cajero
│ │ ├── Tablets validador
│ │ └── Tablets guardarropa
│ └── Red "FestiveraGUEST" (opcional, aislada)
│
└── Switch PoE (si hay puntos fijos)
├── Impresoras QR
└── Puntos de recarga fija
Configuración WiFi recomendada
| Parámetro | Valor recomendado |
| Banda | 5 GHz (menos interferencia en eventos) |
| Canal | 36, 40, 44 o 48 (auto si el router lo soporta) |
| Seguridad | WPA2-PSK o WPA3 |
| SSID operadores | Oculto (no broadcast) |
| Ancho de banda | Mínimo 10 Mbps subida / 20 Mbps bajada |
| IP fija para servidores | Sí (para impresoras y lectores USB fijos) |
Nunca uses la red WiFi del evento abierta al público para los dispositivos de operación. La red de operadores debe estar aislada y con contraseña distinta.
Plan de contingencia — sin internet
Si el internet falla durante el evento:
- 1Activar hotspot 4G del coordinador técnico como backup inmediato.
- 2El validador puede operar con las últimas entradas escaneadas en caché local (IndexedDB) durante hasta 30 minutos.
- 3El cajero debe pausarse hasta recuperar conectividad — no procesar transacciones cashless offline sin confirmación de saldo en Supabase.
- 4Al restaurar conexión, el sistema sincroniza automáticamente los registros pendientes.
5. Servidor y despliegue
Acceso FTP Hostinger
Credenciales FTP
Host212.85.28.245
Puerto21 (con PASV)
Usuariou931838093.app.festivera.com
Raízpublic_html/
ProtocoloFTP con PASV (curl --ftp-pasv)
Subir un archivo al servidor
# Subir un archivo PHP a la raíz
curl --ftp-pasv \
-u "u931838093.app.festivera.com:CONTRASEÑA" \
-T archivo.php \
ftp://212.85.28.245/public_html/api/archivo.php
# Subir admin.html al frontend
curl --ftp-pasv \
-u "u931838093.app.festivera.com:CONTRASEÑA" \
-T admin.html \
ftp://212.85.28.245/public_html/admin.html
# Verificar tamaño en servidor (MLST)
curl --ftp-pasv \
-u "u931838093.app.festivera.com:CONTRASEÑA" \
"ftp://212.85.28.245/public_html/admin.html" \
-I 2>&1 | grep -i size
Estructura de directorios en Hostinger
public_html/
├── admin.html # Panel superadmin
├── dashboard.html # Panel organizador
├── cajero.html # POS cashless
├── validador.html # Portería
├── recargar.html # Recargas pulseras
├── guardarropa-op.html # Guardarropa
├── docs/ # Manuales de usuario
│ ├── index.html
│ ├── manual-organizador.html
│ ├── manual-operador.html
│ ├── manual-administrador.html
│ ├── manual-tecnico.html
│ └── manual-general.html
├── api/
│ ├── bold-webhook.php
│ ├── billing-dispatch.php
│ ├── dian-admin.php # Token: Fstvr2025Wh
│ ├── create-daily-campaigns.php
│ ├── process-email-jobs.php
│ └── billing/dian/
│ ├── Certificate.php
│ ├── DianApi.php
│ ├── DianBilling.php
│ └── UblBuilder.php
└── dian-data/ # FUERA de public_html si es posible
└── certs/tms.p12 # Certificado DIAN
Crons activos en Hostinger
| Cron | Frecuencia | Propósito |
create-daily-campaigns.php | Cada 2 horas | Detecta eventos nuevos, crea campañas de email automáticas |
process-email-jobs.php | Cada minuto | Procesa la cola de emails pendientes vía Resend |
6. Supabase — estructura de datos
Conexión desde PHP
$supabase_url = "https://gpswnzcfyhxzldijgewa.supabase.co/rest/v1";
$headers = [
"apikey: {SERVICE_KEY}",
"Authorization: Bearer {SERVICE_KEY}",
"Content-Type: application/json"
];
# GET con filtro
GET /rest/v1/api_settings?service=eq.dian&select=config
# POST insert
POST /rest/v1/dian_invoices
Body: {"event_id":1,"order_id":2,"amount":50000,...}
# PATCH update
PATCH /rest/v1/dian_invoices?id=eq.42
Body: {"status":"aceptada","cufe":"abc123..."}
Tablas críticas
| Tabla | RLS | Acceso service_role |
| profiles | Sí | ✅ Full |
| events | Sí | ✅ Full |
| orders | Sí | ✅ Full |
| cashless_wallets | Sí | ✅ Full |
| cashless_transactions | Sí | ✅ Full |
| dian_invoices | Deshabilitado (fix pendiente) | ✅ Post-fix |
| api_settings | No | ✅ Full |
| email_campaigns | No | ✅ Full |
El archivo fix-dian-invoices-grant.sql debe ejecutarse en Supabase SQL editor antes de que el panel de Facturas en admin.html funcione correctamente. Ver festivera-platform/sql/fix-dian-invoices-grant.sql.
7. Diagnóstico y troubleshooting
El lector NFC no responde
- 1Confirma que el navegador es Chrome para Android (Web NFC solo funciona ahí).
- 2Ve a Configuración Android → NFC y verifica que esté activado.
- 3El sitio debe estar en
https:// — Web NFC requiere contexto seguro.
- 4En Chrome, la primera vez que se usa NFC, el navegador pide permiso. Acepta el popup.
- 5Si el chip no se lee, prueba acercarlo por distintas zonas del dispositivo (el lector NFC varía por modelo de teléfono).
Error "permission denied for table dian_invoices"
- 1Ve a Supabase Dashboard → SQL Editor.
- 2Ejecuta:
ALTER TABLE public.dian_invoices DISABLE ROW LEVEL SECURITY;
- 3Confirma con:
SELECT count(*) FROM public.dian_invoices;
DIAN InternalServiceFault
| Síntoma | Causa probable | Solución |
| InternalServiceFault en send-test-set | Software "Festivera" "En proceso" en DIAN hab | Llamar 01 8000 912 478, dar TestSetId + NIT |
| Certificate error | Cert .p12 vencido o mal cargado | Subir nuevo .p12 vía admin.html → DIAN → "Subir certificado" |
| ZIP inválido | Nombre de ZIP incorrecto | Verificar DianApi.php usa fv{NIT}{num}.zip |
| Firma XAdES rechazada | Transform C14N faltante | Verificar Certificate.php tiene exc-C14N explícito (ya corregido) |
Email no llega al asistente
- 1Verifica que el cron
process-email-jobs.php esté activo en Hostinger cPanel → Cron Jobs.
- 2Revisa la tabla
email_campaigns en Supabase: el registro debe estar en estado "completado", no "error".
- 3Verifica la API key de Resend en Supabase
api_settings where service='resend'.
- 4Revisa el dashboard de Resend (resend.com) → Logs para ver si el email fue aceptado o rebotó.
Pulsera con saldo incorrecto
- 1Busca el UID de la pulsera en Supabase
cashless_wallets.
- 2Revisa el historial completo en
cashless_transactions filtrando por wallet_id.
- 3Si hay discrepancia, actualiza el saldo vía Supabase SQL:
UPDATE cashless_wallets SET balance=X WHERE uid='...'.
- 4Documenta el ajuste en el log de incidencias del evento.
8. Protocolo de emergencia en vivo
Escenario: el sistema cae en pleno evento
- 1Verificar conexión: abrir app.festivera.com desde otro dispositivo. Si carga, el problema es local (WiFi o dispositivo).
- 2Cambiar a datos móviles: desactivar WiFi en el dispositivo con problemas y usar 4G directamente.
- 3Hotspot de emergencia: el coordinador técnico activa hotspot 4G y reconecta todos los dispositivos.
- 4Modo degradado: el validador puede usar capturas de pantalla de los QR válidos para continuar la entrada manualmente.
- 5Cashless en pausa: si no hay Supabase disponible, pausar puntos de venta hasta restaurar conexión. No procesar cashless sin confirmación.
- 6Escalar: contactar soporte Festivera en hola@festivera.com con captura del error exacto.
Para situaciones de emergencia, el equipo técnico debe siempre tener a mano: (1) hotspot 4G dedicado para operaciones, (2) contraseña FTP, (3) acceso al dashboard de Supabase desde su propio dispositivo.