Sección «Pedir evento» con gestor de reservas y calendario legible en móvil
- /pedir/: calendario de fechas pedidas (negro) y confirmadas (verde), findes tranqui y con ruido, solo findes por defecto con opción de ver la semana entera. Se pincha un día libre y, sin sesión, se pide entrar y se vuelve al formulario con la fecha puesta. - Gestor en Go (gestor/): cuentas por colectivo con argon2id, sesiones y CSRF, cupo y ventana de la asamblea, caducidades, registro append-only. - La cuenta de la asamblea confirma, rechaza, cambia turnos de finde y en «Todas las fechas» quita o modifica cualquier evento. - Orden «cuenta» y «clave» con -clave y -definitiva para fijar contraseñas desde el servidor; ninguna contraseña va en el repositorio. - Botón «Pedir evento» arriba de la barra lateral en todas las páginas. - Calendario semanal en móvil: lista de días con nombre y hora de cada cita. - Fuentes servidas desde fuentes/: fuera Google Fonts en todas las páginas.
This commit is contained in:
parent
c3b51be992
commit
ce3127baff
38 changed files with 4029 additions and 52 deletions
250
gestor/reglas_test.go
Normal file
250
gestor/reglas_test.go
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const hoy = Dia("2026-09-22") // martes
|
||||
|
||||
func solicitud(usuario string, desde, hasta Dia) Solicitud {
|
||||
return Solicitud{
|
||||
Solicitante: usuario,
|
||||
Rol: RolInterno,
|
||||
Titulo: "Concierto de prueba",
|
||||
Desde: desde,
|
||||
Hasta: hasta,
|
||||
Hora: "20:00",
|
||||
}
|
||||
}
|
||||
|
||||
func evento(id, usuario string, desde, hasta Dia, estado Estado) Evento {
|
||||
return Evento{
|
||||
ID: id,
|
||||
Titulo: "Evento " + id,
|
||||
Solicitante: usuario,
|
||||
Desde: desde,
|
||||
Hasta: hasta,
|
||||
Estado: estado,
|
||||
Creado: hoy.MustTime(),
|
||||
}
|
||||
}
|
||||
|
||||
func TestVentanaTresMeses(t *testing.T) {
|
||||
rg := ReglasPorDefecto()
|
||||
|
||||
// 2026-10-02 es viernes, dentro de los tres meses.
|
||||
if err := rg.Validar(solicitud("hacklab", "2026-10-02", "2026-10-02"), nil, hoy); err != nil {
|
||||
t.Fatalf("una fecha dentro de la ventana deberia valer: %v", err)
|
||||
}
|
||||
// 2027-01-01 es viernes y cae fuera: hoy + 3 meses es 2026-12-22.
|
||||
err := rg.Validar(solicitud("hacklab", "2027-01-01", "2027-01-01"), nil, hoy)
|
||||
if err == nil || !strings.Contains(err.Error(), "3 meses") {
|
||||
t.Fatalf("una fecha a mas de 3 meses tiene que fallar por la ventana, dio: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAntelacionMinima(t *testing.T) {
|
||||
rg := ReglasPorDefecto()
|
||||
// 2026-09-25 es el viernes de esta semana: a 3 dias, caducaria al instante.
|
||||
err := rg.Validar(solicitud("hacklab", "2026-09-25", "2026-09-25"), nil, hoy)
|
||||
if err == nil || !strings.Contains(err.Error(), "antelación") {
|
||||
t.Fatalf("pedir con menos de 7 dias tiene que fallar, dio: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCupoTresDiasEnLaVentana(t *testing.T) {
|
||||
rg := ReglasPorDefecto()
|
||||
|
||||
// Tres dias ya cogidos por el mismo colectivo: uno confirmado, dos solicitados.
|
||||
eventos := []Evento{
|
||||
evento("a", "hacklab", "2026-10-02", "2026-10-02", Confirmado),
|
||||
evento("b", "hacklab", "2026-10-10", "2026-10-11", Solicitado),
|
||||
}
|
||||
|
||||
err := rg.Validar(solicitud("hacklab", "2026-10-16", "2026-10-16"), eventos, hoy)
|
||||
if err == nil || !strings.Contains(err.Error(), "cupo") {
|
||||
t.Fatalf("el cuarto dia tiene que fallar por cupo, dio: %v", err)
|
||||
}
|
||||
// El cupo es por colectivo: otro colectivo si puede coger ese dia.
|
||||
if err := rg.Validar(solicitud("feminismos", "2026-10-16", "2026-10-16"), eventos, hoy); err != nil {
|
||||
t.Fatalf("otro colectivo no deberia arrastrar el cupo del primero: %v", err)
|
||||
}
|
||||
// Los dias rechazados o caducados no cuentan.
|
||||
eventos[1].Estado = Caducado
|
||||
if err := rg.Validar(solicitud("hacklab", "2026-10-16", "2026-10-17"), eventos, hoy); err != nil {
|
||||
t.Fatalf("un evento caducado no deberia gastar cupo: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCupoNoCuentaFechasPasadas(t *testing.T) {
|
||||
rg := ReglasPorDefecto()
|
||||
viejos := []Evento{evento("v", "hacklab", "2026-08-07", "2026-08-09", Confirmado)}
|
||||
if err := rg.Validar(solicitud("hacklab", "2026-10-02", "2026-10-04"), viejos, hoy); err != nil {
|
||||
t.Fatalf("lo que ya paso no gasta cupo: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSinSolapes(t *testing.T) {
|
||||
rg := ReglasPorDefecto()
|
||||
eventos := []Evento{evento("a", "hacklab", "2026-10-03", "2026-10-03", Solicitado)}
|
||||
|
||||
err := rg.Validar(solicitud("feminismos", "2026-10-02", "2026-10-04"), eventos, hoy)
|
||||
if err == nil || !strings.Contains(err.Error(), "ya está cogido") {
|
||||
t.Fatalf("un dia solicitado por otro tiene que bloquear, dio: %v", err)
|
||||
}
|
||||
// Al caducar se libera la fecha.
|
||||
eventos[0].Estado = Caducado
|
||||
if err := rg.Validar(solicitud("feminismos", "2026-10-02", "2026-10-04"), eventos, hoy); err != nil {
|
||||
t.Fatalf("una fecha caducada tiene que quedar libre: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSoloFindesSalvoLaEnre(t *testing.T) {
|
||||
rg := ReglasPorDefecto()
|
||||
// 2026-10-06 es martes.
|
||||
err := rg.Validar(solicitud("hacklab", "2026-10-06", "2026-10-06"), nil, hoy)
|
||||
if err == nil || !strings.Contains(err.Error(), "no es día reservable") {
|
||||
t.Fatalf("un martes no es reservable para un colectivo, dio: %v", err)
|
||||
}
|
||||
s := solicitud("enre", "2026-10-06", "2026-10-06")
|
||||
s.Rol = RolEnre
|
||||
if err := rg.Validar(s, nil, hoy); err != nil {
|
||||
t.Fatalf("la enre si puede poner cualquier dia: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLaEnreNoTieneCupoNiVentana(t *testing.T) {
|
||||
rg := ReglasPorDefecto()
|
||||
eventos := []Evento{
|
||||
evento("a", "enre", "2026-10-02", "2026-10-04", Confirmado),
|
||||
evento("b", "enre", "2026-10-09", "2026-10-11", Confirmado),
|
||||
}
|
||||
s := solicitud("enre", "2027-06-04", "2027-06-06")
|
||||
s.Rol = RolEnre
|
||||
if err := rg.Validar(s, eventos, hoy); err != nil {
|
||||
t.Fatalf("la enre no deberia topar con cupo ni ventana: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAmadrinadoPideContacto(t *testing.T) {
|
||||
rg := ReglasPorDefecto()
|
||||
s := solicitud("hacklab", "2026-10-02", "2026-10-02")
|
||||
s.Amadrinado = "Colectivo de fuera"
|
||||
err := rg.Validar(s, nil, hoy)
|
||||
if err == nil || !strings.Contains(err.Error(), "contacto") {
|
||||
t.Fatalf("amadrinar sin contacto tiene que fallar, dio: %v", err)
|
||||
}
|
||||
s.Contacto = "fuera@riseup.net"
|
||||
if err := rg.Validar(s, nil, hoy); err != nil {
|
||||
t.Fatalf("con contacto deberia valer: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaducidad(t *testing.T) {
|
||||
rg := ReglasPorDefecto()
|
||||
ahora := hoy.MustTime()
|
||||
|
||||
cerca := evento("a", "hacklab", "2026-09-27", "2026-09-27", Solicitado)
|
||||
if m := rg.Caducidad(cerca, ahora); m == "" || !strings.Contains(m, "menos de 7") {
|
||||
t.Fatalf("a 5 dias sin confirmar tiene que caducar, dio %q", m)
|
||||
}
|
||||
|
||||
lejos := evento("b", "hacklab", "2026-11-06", "2026-11-06", Solicitado)
|
||||
if m := rg.Caducidad(lejos, ahora); m != "" {
|
||||
t.Fatalf("una solicitud reciente y lejana no caduca, dio %q", m)
|
||||
}
|
||||
|
||||
vieja := lejos
|
||||
vieja.Creado = ahora.AddDate(0, 0, -31)
|
||||
if m := rg.Caducidad(vieja, ahora); m == "" || !strings.Contains(m, "esperando") {
|
||||
t.Fatalf("31 dias esperando tiene que caducar, dio %q", m)
|
||||
}
|
||||
|
||||
confirmada := cerca
|
||||
confirmada.Estado = Confirmado
|
||||
if m := rg.Caducidad(confirmada, ahora); m != "" {
|
||||
t.Fatalf("un evento confirmado no caduca nunca, dio %q", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTurnoDeFinde(t *testing.T) {
|
||||
rg := ReglasPorDefecto() // ancla 2026-09-25, turno ruido
|
||||
|
||||
casos := []struct {
|
||||
dia Dia
|
||||
quer Turno
|
||||
}{
|
||||
{"2026-09-25", Ruido}, // viernes ancla
|
||||
{"2026-09-26", Ruido}, // sabado del mismo finde
|
||||
{"2026-09-27", Ruido}, // domingo del mismo finde
|
||||
{"2026-10-02", Tranqui}, // finde siguiente
|
||||
{"2026-10-09", Ruido},
|
||||
{"2026-10-16", Tranqui},
|
||||
{"2026-09-18", Tranqui}, // hacia atras tambien alterna
|
||||
}
|
||||
for _, c := range casos {
|
||||
if got := rg.TurnoDe(c.dia, nil); got != c.quer {
|
||||
t.Errorf("%s: turno %q, esperaba %q", c.dia, got, c.quer)
|
||||
}
|
||||
}
|
||||
|
||||
// Un dia de entre semana no tiene turno.
|
||||
if got := rg.TurnoDe("2026-10-06", nil); got != "" {
|
||||
t.Errorf("un martes no tiene turno de finde, dio %q", got)
|
||||
}
|
||||
|
||||
// La excepcion escrita a mano manda sobre la alternancia.
|
||||
exc := map[Dia]Turno{"2026-10-02": Ruido}
|
||||
if got := rg.TurnoDe("2026-10-03", exc); got != Ruido {
|
||||
t.Errorf("la excepcion del viernes tiene que valer para todo el finde, dio %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangoYDiaISO(t *testing.T) {
|
||||
dias := Rango("2026-10-02", "2026-10-04")
|
||||
if len(dias) != 3 || dias[0] != "2026-10-02" || dias[2] != "2026-10-04" {
|
||||
t.Fatalf("rango mal calculado: %v", dias)
|
||||
}
|
||||
if Rango("2026-10-04", "2026-10-02") != nil {
|
||||
t.Fatal("un rango invertido tiene que dar nada")
|
||||
}
|
||||
if got := Dia("2026-10-02").DiaISO(); got != 5 {
|
||||
t.Fatalf("2026-10-02 es viernes, iso 5, dio %d", got)
|
||||
}
|
||||
if got := ViernesDe("2026-10-04"); got != "2026-10-02" {
|
||||
t.Fatalf("el viernes del domingo 4 es el 2, dio %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVentanaDevuelveTresMeses(t *testing.T) {
|
||||
rg := ReglasPorDefecto()
|
||||
primero, ultimo := rg.Ventana(hoy)
|
||||
if primero != "2026-09-29" {
|
||||
t.Errorf("primera fecha pedible %q, esperaba 2026-09-29", primero)
|
||||
}
|
||||
if ultimo != "2026-12-22" {
|
||||
t.Errorf("ultima fecha pedible %q, esperaba 2026-12-22", ultimo)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModificarNoChocaConsigoMismo(t *testing.T) {
|
||||
rg := ReglasPorDefecto()
|
||||
eventos := []Evento{
|
||||
evento("a", "hacklab", "2026-10-02", "2026-10-03", Confirmado),
|
||||
evento("b", "vivienda", "2026-10-09", "2026-10-09", Solicitado),
|
||||
}
|
||||
|
||||
// La asamblea mueve la hora del evento «a» sin cambiarle los días.
|
||||
s := solicitud("hacklab", "2026-10-02", "2026-10-03")
|
||||
s.Rol, s.Excluir = RolEnre, "a"
|
||||
if err := rg.Validar(s, eventos, hoy); err != nil {
|
||||
t.Fatalf("modificar un evento en sus propios días deberia valer: %v", err)
|
||||
}
|
||||
|
||||
// Pero no puede llevarlo encima del evento «b».
|
||||
s.Desde, s.Hasta = "2026-10-09", "2026-10-09"
|
||||
if err := rg.Validar(s, eventos, hoy); err == nil || !strings.Contains(err.Error(), "cogido") {
|
||||
t.Fatalf("mover «a» encima de «b» deberia fallar por solape, dio %v", err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue