/* Standup diario (27-oct-2026). Servidor: backend/standup.py. Mi standup: GET /api/standup/hoy → instrucciones del bot + preguntas por pendiente; POST /api/standup/respuesta. Si la persona lo deja marcado, mueve sus pendientes con PATCH /api/tarea?id= (queda a su nombre en el historial). Equipo (Leo, Adriana, el bot): GET /api/standup?fecha= . La dirección guarda la vista: ?vista=equipo&fecha=AAAA-MM-DD. */ (function () { const $ = id => document.getElementById(id); const esc = s => { const d = document.createElement('div'); d.textContent = s == null ? '' : String(s); return d.innerHTML; }; const MES = ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']; const DIA = ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado']; const largo = f => { const d = new Date(f + 'T12:00:00'); return isNaN(d) ? f : DIA[d.getDay()] + ' ' + d.getDate() + ' ' + MES[d.getMonth()]; }; const hora = iso => (iso || '').slice(11, 16); const ESTADO_T = { 'por-hacer': 'Por hacer', 'en-curso': 'En curso', bloqueado: 'Bloqueado', hecho: 'Hecho' }; const OPC = [['hecho', 'Hecho'], ['sigo', 'Sigo con él'], ['bloqueado', 'Bloqueado'], ['no-empece', 'No empecé']]; const A_TABLERO = { hecho: 'hecho', sigo: 'en-curso', bloqueado: 'bloqueado' }; const params = new URLSearchParams(location.search); let vista = params.get('vista') === 'equipo' ? 'equipo' : 'mio', puedeEquipo = false; async function api(url, op) { const r = await fetch(url, Object.assign({ credentials: 'same-origin', cache: 'no-store' }, op || {})); const j = await r.json().catch(() => ({ ok: false, error: 'HTTP ' + r.status })); if (!r.ok || !j.ok) { const e = new Error(j.error || ('HTTP ' + r.status)); e.status = r.status; throw e; } return j; } const json = (method, body) => ({ method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); function aviso(t, bien) { const a = $('aviso'); a.className = 'msg ' + (bien ? 'ok' : 'err'); a.innerHTML = t; a.scrollIntoView({ block: 'nearest' }); } function botones() { $('vistas').innerHTML = '' + (puedeEquipo ? '' : ''); $('vistas').querySelectorAll('button').forEach(b => b.addEventListener('click', () => { vista = b.dataset.v; const p = new URLSearchParams(location.search); p.set('vista', vista); history.replaceState(null, '', '?' + p); botones(); (vista === 'equipo' ? equipo : mio)(); })); } async function mio() { const C = $('cont'); C.innerHTML = '

Cargando…

'; let j; try { j = await api('/api/standup/hoy'); } catch (e) { C.innerHTML = '
' + esc(e.message) + '
'; return; } const s = j.standup; $('meta').textContent = largo(s.fecha) + (s.fecha !== j.hoy ? ' (pendiente de contestar)' : '') + ' · ' + (s.nombre || s.usuario); let h = ''; if (s.respuesta) { h += '
Ya contestaste a las ' + esc(hora(s.respuesta.enviada_en)) + '. Gracias. Puedes corregir abajo y volver a enviar.
'; } if ((s.instrucciones || []).length || s.nota_bot) { h += '

Tus instrucciones de hoy

    ' + (s.instrucciones || []).map(i => '
  1. ' + esc(i.texto) + (i.liga ? ' — abrir' : '') + (i.tarea_id ? ' ' + esc(i.tarea_id) + '' : '') + '
  2. ').join('') + '
' + (s.nota_bot ? '

' + esc(s.nota_bot) + '

' : '') + '

Las preparó ' + esc(s.preparado_por || 'el bot') + ' el ' + esc(largo((s.preparado_en || '').slice(0, 10))) + ' a las ' + esc(hora(s.preparado_en)) + ', con el tablero de ese momento.

'; } else { h += '

Hoy el bot no dejó instrucciones; contesta sobre tus pendientes abiertos.

'; } const previas = {}; ((s.respuesta || {}).por_tarea || []).forEach(p => { previas[p.tarea_id] = p; }); const preg = s.preguntas || []; h += '

Tus pendientes

'; if (!preg.length) h += '

No tienes pendientes abiertos a tu nombre. Si estás trabajando en algo que no está en el tablero, cuéntalo abajo.

'; preg.forEach((p, i) => { const prev = previas[p.tarea_id] || {}; h += '
' + '
' + esc(p.tarea_id) + ' · ' + esc(ESTADO_T[p.estado_tablero] || p.estado_tablero || '') + (p.vence ? ' · vence ' + esc(p.vence) : '') + '
' + (p.pregunta ? '
' + esc(p.pregunta) + '
' : '') + '
' + OPC.map(o => '').join('') + '
' + '
'; }); h += '
'; const r = s.respuesta || {}; h += '

Las tres preguntas

' + '' + '' + '' + '' + '
' + '
'; C.innerHTML = h; $('bEnviar').addEventListener('click', () => enviar(s, preg)); } async function enviar(s, preg) { const b = $('bEnviar'); b.disabled = true; b.textContent = 'Enviando…'; const por_tarea = []; document.querySelectorAll('.su-p').forEach((d, i) => { const o = d.querySelector('input[type=radio]:checked'); if (o) por_tarea.push({ tarea_id: d.dataset.id, estado: o.value, nota: d.querySelector('input.nota').value.trim() }); }); try { await api('/api/standup/respuesta', json('POST', { fecha: s.fecha, por_tarea, hecho: $('qHecho').value.trim(), falta: $('qFalta').value.trim(), bloqueos: $('qBloq').value.trim(), comentario: $('qCom').value.trim() })); let movidos = 0, fallas = []; if ($('mover').checked) { for (const p of por_tarea) { const q = preg.find(x => x.tarea_id === p.tarea_id) || {}; const destino = A_TABLERO[p.estado]; if (!destino || destino === q.estado_tablero) continue; try { await api('/api/tarea?id=' + encodeURIComponent(p.tarea_id), json('PATCH', { estado: destino, _nota: 'desde el standup' + (p.nota ? ': ' + p.nota : '') })); movidos++; } catch (e) { fallas.push(p.tarea_id + ' (' + e.message + ')'); } } } aviso('Listo, gracias. ' + (movidos ? movidos + ' pendiente' + (movidos > 1 ? 's' : '') + ' movido' + (movidos > 1 ? 's' : '') + ' en el tablero. ' : '') + (fallas.length ? 'No pude mover: ' + esc(fallas.join(', ')) + ' — muévelos a mano en Pendientes.' : ''), !fallas.length); document.querySelectorAll('.hg-standup').forEach(x => x.remove()); await mio(); } catch (e) { aviso(esc(e.message)); b.disabled = false; b.textContent = 'Enviar mi standup'; } } async function equipo(fecha) { const C = $('cont'); C.innerHTML = '

Cargando…

'; fecha = fecha || params.get('fecha') || ''; let j; try { j = await api('/api/standup' + (fecha ? '?fecha=' + encodeURIComponent(fecha) : '')); } catch (e) { C.innerHTML = '
' + esc(e.message) + '
'; return; } $('meta').textContent = largo(j.fecha) + ' · contestaron ' + j.contestaron + ' de ' + j.total; let h = '
'; if (!j.standups.length) h += '

El bot no preparó standup ese día.

'; else { const bloqueos = j.standups.filter(s => s.respuesta && (s.respuesta.bloqueos || (s.respuesta.por_tarea || []).some(p => p.estado === 'bloqueado'))); if (bloqueos.length) h += '

Bloqueos

'; h += '
' + j.standups.sort((a, b) => (b.respuesta ? 1 : 0) - (a.respuesta ? 1 : 0) || String(a.nombre).localeCompare(b.nombre)).map(s => { const r = s.respuesta; return ''; }).join('') + '
PersonaContestóTerminóLe faltaPendientes
' + esc(s.nombre) + '' + (r ? esc(hora(r.enviada_en)) : '—') + '' + esc(r ? r.hecho : '') + '' + esc(r ? r.falta : '') + '' + (r ? (r.por_tarea || []).map(p => esc(p.tarea_id) + ': ' + esc(p.estado) + (p.nota ? ' — ' + esc(p.nota) : '')).join('
') : esc((s.preguntas || []).length + ' preguntas')) + '
'; } C.innerHTML = h; $('fFecha').addEventListener('change', e => { const p = new URLSearchParams(location.search); p.set('fecha', e.target.value); history.replaceState(null, '', '?' + p); equipo(e.target.value); }); } (async () => { try { const w = await api('/api/whoami'); puedeEquipo = !!w.admin || ['leo', 'adriana', 'claude', 'gibranlp'].includes((w.user || '').toLowerCase()); } catch (e) {} if (vista === 'equipo' && !puedeEquipo) vista = 'mio'; botones(); (vista === 'equipo' ? equipo : mio)(); })(); })();