ToDs Lab

Registro de Recepción :root { –bg: #f5f0e8; –surface: #fffdf8; –border: #d6cfc0; –accent: #1a3a2a; –accent2: #c8a96e; –text: #1c1c1c; –text-muted: #7a7060; –danger: #b84040; –row-hover: #f0ead8; } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: ‘DM Mono’, monospace; background: var(–bg); color: var(–text); min-height: 100vh; padding: 2rem; } header { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: 2.5rem; padding-bottom: 1.5rem; border-bottom: 2px solid var(–accent); } .header-left h1 { font-family: ‘DM Serif Display’, serif; font-size: 2.4rem; color: var(–accent); letter-spacing: -0.5px; line-height: 1; } .header-left p { font-size: 0.75rem; color: var(–text-muted); margin-top: 0.4rem; letter-spacing: 0.08em; text-transform: uppercase; } .date-badge { background: var(–accent); color: #fff; padding: 0.5rem 1.2rem; font-size: 0.85rem; letter-spacing: 0.06em; display: flex; flex-direction: column; align-items: flex-end; gap: 0.1rem; } .date-badge span:first-child { font-size: 1.4rem; font-family: ‘DM Serif Display’, serif; line-height: 1; } /* Formulario de nueva entrada */ .form-card { background: var(–surface); border: 1.5px solid var(–border); padding: 1.5rem 2rem; margin-bottom: 2rem; } .form-title { font-size: 0.7rem; letter-spacing: 0.15em; text-transform: uppercase; color: var(–accent2); margin-bottom: 1rem; font-weight: 500; } .form-row { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr 1fr auto; gap: 0.75rem; align-items: end; } .field { display: flex; flex-direction: column; gap: 0.3rem; } .field label { font-size: 0.65rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(–text-muted); } .field input { font-family: ‘DM Mono’, monospace; font-size: 0.9rem; padding: 0.5rem 0.7rem; border: 1.5px solid var(–border); background: var(–bg); color: var(–text); outline: none; transition: border-color 0.15s; width: 100%; } .field input:focus { border-color: var(–accent); background: #fff; } .btn-add { font-family: ‘DM Mono’, monospace; font-size: 0.85rem; padding: 0.52rem 1.4rem; background: var(–accent); color: #fff; border: none; cursor: pointer; letter-spacing: 0.05em; transition: background 0.15s; white-space: nowrap; height: fit-content; } .btn-add:hover { background: #254d38; } /* Tabla */ .table-wrap { background: var(–surface); border: 1.5px solid var(–border); overflow-x: auto; } .table-header { display: flex; justify-content: space-between; align-items: center; padding: 0.9rem 1.5rem; border-bottom: 1.5px solid var(–border); } .table-header span { font-size: 0.7rem; letter-spacing: 0.15em; text-transform: uppercase; color: var(–accent2); font-weight: 500; } .entry-count { font-size: 0.75rem; color: var(–text-muted); } table { width: 100%; border-collapse: collapse; font-size: 0.85rem; } thead tr { background: var(–accent); color: #fff; } thead th { padding: 0.7rem 1rem; text-align: left; font-size: 0.65rem; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 500; } tbody tr { border-bottom: 1px solid var(–border); transition: background 0.1s; } tbody tr:hover { background: var(–row-hover); } tbody tr:last-child { border-bottom: none; } tbody td { padding: 0.65rem 1rem; color: var(–text); } .td-hab { font-family: ‘DM Serif Display’, serif; font-size: 1.1rem; color: var(–accent); } .td-hora { color: var(–text-muted); } .btn-del { background: none; border: 1px solid var(–border); color: var(–text-muted); font-size: 0.7rem; font-family: ‘DM Mono’, monospace; padding: 0.2rem 0.6rem; cursor: pointer; transition: all 0.15s; letter-spacing: 0.05em; } .btn-del:hover { border-color: var(–danger); color: var(–danger); } /* Fila lista */ tr.lista { opacity: 0.45; background: #e8e2d4 !important; } tr.lista td { text-decoration: line-through; color: var(–text-muted); } tr.lista .td-hab { color: var(–text-muted); } /* Checkbox hab lista */ .check-lista { width: 1.2rem; height: 1.2rem; accent-color: var(–accent); cursor: pointer; } .empty-state { text-align: center; padding: 3rem; color: var(–text-muted); font-size: 0.8rem; letter-spacing: 0.08em; } /* Acciones inferiores */ .actions { display: flex; gap: 1rem; margin-top: 1.5rem; justify-content: flex-end; } .btn-secondary { font-family: ‘DM Mono’, monospace; font-size: 0.8rem; padding: 0.5rem 1.2rem; background: transparent; border: 1.5px solid var(–accent); color: var(–accent); cursor: pointer; letter-spacing: 0.05em; transition: all 0.15s; } .btn-secondary:hover { background: var(–accent); color: #fff; } .btn-gold { border-color: var(–accent2); color: var(–accent2); } .btn-gold:hover { background: var(–accent2); color: #fff; } @media (max-width: 900px) { .form-row { grid-template-columns: 1fr 1fr; } body { padding: 1rem; } } /* ── Modal email ───────────────────────────────────────────── */ .modal-overlay { display: none; position: fixed; inset: 0; background: rgba(10,20,15,0.55); z-index: 100; align-items: center; justify-content: center; } .modal-overlay.open { display: flex; } .modal { background: var(–surface); border: 1.5px solid var(–border); width: 100%; max-width: 540px; margin: 1rem; box-shadow: 0 8px 40px rgba(0,0,0,0.18); animation: slideUp 0.2s ease; } @keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .modal-head { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.5rem; border-bottom: 1.5px solid var(–border); background: var(–accent); color: #fff; } .modal-head span { font-size: 0.7rem; letter-spacing: 0.15em; text-transform: uppercase; } .modal-close { background: none; border: none; color: #fff; font-size: 1.3rem; cursor: pointer; line-height: 1; padding: 0 0.2rem; } .modal-body { padding: 1.5rem; display: flex; flex-direction: column; gap: 1rem; } .modal-field { display: flex; flex-direction: column; gap: 0.3rem; } .modal-field label { font-size: 0.65rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(–text-muted); } .modal-field input, .modal-field textarea { font-family: ‘DM Mono’, monospace; font-size: 0.85rem; padding: 0.55rem 0.8rem; border: 1.5px solid var(–border); background: var(–bg); color: var(–text); outline: none; transition: border-color 0.15s; width: 100%; resize: vertical; } .modal-field input:focus, .modal-field textarea:focus { border-color: var(–accent); background: #fff; } .modal-preview { background: var(–bg); border: 1px dashed var(–border); padding: 1rem; font-size: 0.8rem; line-height: 1.7; color: var(–text); white-space: pre-wrap; } .modal-preview-label { font-size: 0.6rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(–accent2); margin-bottom: 0.4rem; } .modal-foot { display: flex; justify-content: flex-end; gap: 0.75rem; padding: 1rem 1.5rem; border-top: 1.5px solid var(–border); } .btn-email { font-family: ‘DM Mono’, monospace; font-size: 0.8rem; padding: 0.5rem 1.4rem; background: var(–accent); color: #fff; border: none; cursor: pointer; letter-spacing: 0.05em; transition: background 0.15s; } .btn-email:hover { background: #254d38; } .btn-email-row { background: none; border: 1px solid var(–accent2); color: var(–accent2); font-size: 0.7rem; font-family: ‘DM Mono’, monospace; padding: 0.2rem 0.7rem; cursor: pointer; transition: all 0.15s; letter-spacing: 0.04em; white-space: nowrap; } .btn-email-row:hover { background: var(–accent2); color: #fff; }

Registro de Recepción

Control diario de llegadas

+ Nueva entrada
Apellido del cliente
Habitación (3 cifras)
Hora de llegada
Timing HK
Timing RS
Entradas del día 0 registros
# Apellido Hab. Hora llegada Timing HK Timing RS Hab. lista Email
Sin registros aún. Añade la primera entrada.
// ── Fecha ────────────────────────────────────────────────────── const now = new Date(); const dias = [‘Domingo’,’Lunes’,’Martes’,’Miércoles’,’Jueves’,’Viernes’,’Sábado’]; const meses = [‘Enero’,’Febrero’,’Marzo’,’Abril’,’Mayo’,’Junio’,’Julio’,’Agosto’,’Septiembre’,’Octubre’,’Noviembre’,’Diciembre’]; document.getElementById(‘dia-num’).textContent = `${dias[now.getDay()]} ${now.getDate()}`; document.getElementById(‘mes-ano’).textContent = `${meses[now.getMonth()]} ${now.getFullYear()}`; const HOY = now.toISOString().slice(0, 10); const STORAGE_KEY = `recepcion_${HOY}`; // ── Datos ────────────────────────────────────────────────────── let entries = []; function load() { const raw = localStorage.getItem(STORAGE_KEY); entries = raw ? JSON.parse(raw) : []; render(); } function save() { localStorage.setItem(STORAGE_KEY, JSON.stringify(entries)); } // ── Añadir ───────────────────────────────────────────────────── function addEntry() { const apellido = document.getElementById(‘inp-apellido’).value.trim(); const hab = document.getElementById(‘inp-hab’).value.trim(); const llegada = document.getElementById(‘inp-llegada’).value; const hk = document.getElementById(‘inp-hk’).value; const rs = document.getElementById(‘inp-rs’).value; if (!apellido) { alert(‘Introduce el apellido del cliente.’); return; } if (!/^\d{3}$/.test(hab)) { alert(‘La habitación debe tener exactamente 3 dígitos.’); return; } entries.push({ apellido, hab, llegada, hk, rs, lista: false }); save(); render(); clearForm(); } function clearForm() { [‘inp-apellido’,’inp-hab’,’inp-llegada’,’inp-hk’,’inp-rs’].forEach(id => { document.getElementById(id).value = ”; }); document.getElementById(‘inp-apellido’).focus(); } // ── Eliminar ─────────────────────────────────────────────────── function deleteEntry(i) { if (!confirm(‘¿Eliminar este registro?’)) return; entries.splice(i, 1); save(); render(); } // ── Toggle hab. lista ────────────────────────────────────────── function toggleLista(origIndex, checked) { entries[origIndex].lista = checked; save(); render(); } // ── Renderizar ───────────────────────────────────────────────── function render() { const tbody = document.getElementById(‘tabla-body’); document.getElementById(‘entry-count’).textContent = `${entries.length} registro${entries.length !== 1 ? ‘s’ : ”}`; if (entries.length === 0) { tbody.innerHTML = `Sin registros aún. Añade la primera entrada.`; return; } // Pendientes primero, listos al final const sorted = entries .map((e, i) => ({ …e, _orig: i })) .sort((a, b) => (a.lista === b.lista ? 0 : a.lista ? 1 : -1)); tbody.innerHTML = sorted.map((e, i) => ` ${i + 1} ${e.apellido} ${e.hab} ${e.llegada || ‘—’} ${e.hk || ‘—’} ${e.rs || ‘—’} `).join(”); } // ── Limpiar día ──────────────────────────────────────────────── function clearDay() { if (!confirm(‘¿Borrar todos los registros de hoy?’)) return; entries = []; save(); render(); } // ── Exportar CSV ─────────────────────────────────────────────── function exportCSV() { if (entries.length === 0) { alert(‘No hay registros para exportar.’); return; } const header = ‘Fecha,Apellido,Habitación,Hora llegada,Timing HK,Timing RS,Hab. lista’; const rows = entries.map(e => `${HOY},”${e.apellido}”,${e.hab},${e.llegada || ”},${e.hk || ”},${e.rs || ”},${e.lista ? ‘Sí’ : ‘No’}` ); const csv = [header, …rows].join(‘\n’); const blob = new Blob([csv], { type: ‘text/csv;charset=utf-8;’ }); const url = URL.createObjectURL(blob); const a = document.createElement(‘a’); a.href = url; a.download = `recepcion_${HOY}.csv`; a.click(); URL.revokeObjectURL(url); } // ── Enter para añadir ────────────────────────────────────────── document.addEventListener(‘keydown’, e => { if (e.key === ‘Enter’) addEntry(); }); // ── Modal email ──────────────────────────────────────────────── let modalEntry = null; function openModal(i) { modalEntry = entries[i]; const meses_fr = [‘janvier’,’février’,’mars’,’avril’,’mai’,’juin’,’juillet’,’août’,’septembre’,’octobre’,’novembre’,’décembre’]; const dateStr = `${now.getDate()} ${meses_fr[now.getMonth()]} ${now.getFullYear()}`; const asunto = `Informations — Chambre ${modalEntry.hab}`; const mensaje = `Madame, Monsieur ${modalEntry.apellido}, Nous avons le plaisir de vous accueillir et vous communiquons ci-dessous les informations relatives à votre chambre : • Chambre n° : ${modalEntry.hab} • Date d’arrivée : ${dateStr}${modalEntry.llegada ? `\n • Heure d’arrivée : ${modalEntry.llegada}` : ”}${modalEntry.hk ? `\n • Mise à disposition (HK) : ${modalEntry.hk}` : ”}${modalEntry.rs ? `\n • Room Service disponible à partir de : ${modalEntry.rs}` : ”} N’hésitez pas à contacter la réception pour toute demande. Nous vous souhaitons un agréable séjour. L’équipe de la Réception`; document.getElementById(‘modal-asunto’).value = asunto; document.getElementById(‘modal-preview’).textContent = mensaje; document.getElementById(‘modal-dest’).value = ”; document.getElementById(‘modal-overlay’).classList.add(‘open’); setTimeout(() => document.getElementById(‘modal-dest’).focus(), 100); } function closeModal() { document.getElementById(‘modal-overlay’).classList.remove(‘open’); modalEntry = null; } function sendEmail() { const dest = document.getElementById(‘modal-dest’).value.trim(); const asunto = document.getElementById(‘modal-asunto’).value.trim(); const cuerpo = document.getElementById(‘modal-preview’).textContent; if (!dest) { alert(‘Veuillez saisir l\’adresse email du client.’); return; } const mailto = `mailto:${dest}?subject=${encodeURIComponent(asunto)}&body=${encodeURIComponent(cuerpo)}`; window.location.href = mailto; closeModal(); } // Cerrar modal con Escape document.addEventListener(‘keydown’, e => { if (e.key === ‘Escape’) closeModal(); }); // ── Init ─────────────────────────────────────────────────────── load();