Volver a proyectos

// Expediente de evidencia

Cabañas Las Palmas

Sistema de gestión para un complejo de cabañas: vitrina pública de consulta y panel privado para registrar huéspedes, reservas, pagos y reportes. En producción activa; las garantías de integridad aún tienen límites identificados.

Next.js 16React 19TypeScriptSQLite WALVitestExcelJSPDFKit
Vista previa del proyecto en video.
Para quién

Dos personas administradoras registran clientes y gestionan la operación del complejo. Los visitantes ven cabañas y consultan por WhatsApp; no reservan ni pagan directamente en la web pública.

Mi responsabilidad

Diseñé y desarrollé íntegramente el sitio, el panel, las API, la base de datos y el despliegue, con puesta en marcha a finales de junio.

Estado actual

En producción activa y utilizado por dos administradoras desde finales de junio. Esta revisión verificó código y ejecución local; no auditó el servidor en producción.

Qué permite hacer

Unifica el registro de cabañas, pasajeros, reservas, abonos, gastos y reportes. No hay una medición acreditada de tiempo ahorrado ni una garantía de ausencia de sobreventas.

Contexto

Cabañas Las Palmas combina una vitrina pública con una herramienta privada de recepción. Dos personas administradoras la usan en su día a día para registrar clientes. La página pública presenta unidades, galería y contacto por WhatsApp; no publica disponibilidad calculada ni un checkout de reserva.

Problema

Con un inventario pequeño de unidades, recepción necesita relacionar cada estancia con su cabaña, huésped, fechas y dinero recibido. Un listado visual sirve de poco si la creación, los cambios de estado y los cobros pueden dejar datos contradictorios. El objetivo del sistema es concentrar esas tareas; la eliminación total de sobreventas o errores de cobro no está demostrada.

Mi rol

Diseñé y desarrollé todo el proyecto: experiencia pública, panel, lógica de servidor, persistencia y despliegue. Es un trabajo integral y autónomo, reflejado en la arquitectura y componentes del repositorio.

Restricciones

La arquitectura de un solo servidor y SQLite evita mantener una base de datos separada, pero exige una ubicación persistente para base, imágenes y respaldos, y una política real de restauración. Las fechas de estadía son días de calendario; salida y entrada en la misma fecha deben ser compatibles. La información personal de huéspedes y pagos permanece fuera de esta ficha y de las pruebas.

Solución

El sitio público muestra cabañas activas, fotos y servicios, y abre una conversación de WhatsApp para consultar. En el panel, una persona autenticada registra pasajeros, crea reservas para una cabaña y fechas, consulta la ocupación en tablas, registra pagos y extras y obtiene reportes o exportaciones. El correo de comprobante tras crear una reserva es opcional: un fallo de entrega no revierte la reserva guardada. No hay pasarela de pago ni reserva autónoma del visitante.

Arquitectura y diagramas

Una aplicación Next.js 16 con React 19 separa la presentación pública de las vistas privadas. Los Route Handlers validan entrada y permisos, y better-sqlite3 persiste cabañas, pasajeros, reservas y pagos en SQLite con WAL y claves foráneas. La creación y edición completa de reservas usa BEGIN IMMEDIATE para verificar solapamiento e insertar dentro de una escritura serializada. El contenido público lee las cabañas visibles, no el calendario de ocupación. ExcelJS genera exportaciones autorizadas; PDFKit y Nodemailer intervienen en comprobantes y notificaciones según el flujo y su configuración.

Flujo de vitrina pública, gestión y persistencia
Flujo de vitrina pública, gestión y persistenciaEl visitante consulta cabañas y tarifas en la vitrina pública y contacta por WhatsApp para coordinar su estadía; recepción confirma disponibilidad y tarifas antes de registrar. La operadora autentica en el panel de gestión con sesión JWT y opera reservas, abonos y extras sobre los módulos de negocio. Las transacciones inmediatas se guardan en SQLite con WAL; las imágenes y respaldos van al disco local. Desde SQLite se emiten comprobantes en PDF y reportes en Excel.recepción confirma disponibilidadSesión JWTLectura / escrituraDisco localPDFKitExcelJS · XLSXVisitanteVitrina públicaNext.js · App RouterCanal WhatsAppOperadoraPanel de gestiónNext.js · JWT HttpOnlyMódulos de negocioReservas · abonos · extrasComprobantesPDFKit · NodemailerReportesExcelJS · XLSXSQLiteWAL · transaccionesArchivos discoSharp · respaldosFlujo de vitrina pública, gestión y persistenciaEl visitante consulta cabañas y tarifas en la vitrina pública y contacta por WhatsApp para coordinar su estadía; recepción confirma disponibilidad y tarifas antes de registrar. La operadora autentica en el panel de gestión con sesión JWT y opera reservas, abonos y extras sobre los módulos de negocio. Las transacciones inmediatas se guardan en SQLite con WAL; las imágenes y respaldos van al disco local. Desde SQLite se emiten comprobantes en PDF y reportes en Excel.recepción confirma disponibilidadSesión JWTVisitanteVitrina públicaNext.js · App RouterCanal WhatsAppOperadoraPanel de gestiónNext.js · JWT HttpOnlyMódulos de negocioReservas · abonos · extrasSQLiteWALArchivos discoSharp · backupsComprobantesPDFKitReportesExcelJS
Leer el diagrama y sus límites

1. Recorrido y actores: El visitante explora la vitrina pública (Next.js App Router) e inicia el contacto por WhatsApp. La operadora recibe la solicitud, confirma disponibilidad y accede al panel privado con sesión JWT en cookie HttpOnly.

2. Componentes y conexiones: Las flechas continuas indican transferencias activas (navegación pública, autenticación, operaciones de reservas y pagos). Las flechas a almacenes registran transacciones inmediatas en SQLite (better-sqlite3 con modo WAL) y archivos en disco local (Sharp).

3. Salidas operativas: Los comprobantes PDF (PDFKit) y los reportes de ocupación/ingresos (ExcelJS) se generan bajo demanda desde la base de datos para la operadora.

4. Estado de verificación: Sistema operando en producción con datos reales; las comprobaciones locales verificaron endpoints HTTP, esquema de base de datos, generación de PDF y exportación XLSX sin pasarela externa.

5. Límites y defecto conocido: La reactivación de reservas canceladas omite la validación de colisiones, permitiendo solapamientos de fechas (defecto reproducido y documentado). El envío de correos vía Nodemailer es opcional y no se probó con servidor SMTP en vivo.

Mapa de implementación verificado en producción y entorno local. Las reservas no usan pasarelas de pago automatizadas; la coordinación de disponibilidad se realiza manualmente vía WhatsApp.

Flujo crítico de reserva

1. Recepción inicia sesión y abre el registro de reservas; el navegador carga cabañas, pasajeros y reservas desde controladores privados.

2. La persona elige huésped, unidad y fechas. El servidor valida identificadores, fechas y tarifa.

3. Una transacción inmediata comprueba la existencia de huésped y cabaña activa, busca una estadía activa que se cruce y, si no la hay, inserta la nueva reserva con noches y total. Un solapamiento devuelve conflicto.

4. La respuesta se refleja en el listado. Recepción puede registrar un abono; su controlador vuelve a calcular el saldo dentro de otra transacción y persiste el pago.

5. La notificación de reserva se intenta después de confirmar la transacción y puede omitirse o fallar sin borrar la reserva. Este recorrido no incluye cobro externo ni aceptación automática del visitante.

Concurrencia e integridad de datos

La ruta principal de creación serializa la lectura de disponibilidad y la inserción con BEGIN IMMEDIATE; usa intervalos de fechas semiabiertos, por lo que una salida y una entrada el mismo día no colisionan. Ese mecanismo tiene un límite importante: un cambio de estado sin cambiar fechas actualiza la reserva fuera de esa comprobación y puede reactivar una estancia que se solapa. Las rutas de pagos tampoco conservan una identidad de operación para reconocer un reintento tras una respuesta perdida; dos abonos válidos pueden duplicarse. La restauración y el almacenamiento persistente del arranque requieren verificación adicional. Por estas rutas abiertas no corresponde prometer cero sobreventas, idempotencia o recuperación probada.

Decisiones de ingeniería

SQLite con WAL y transacciones inmediatas encaja con una instancia y un volumen reducido de escritura, a condición de mantener el archivo en almacenamiento local persistente y controlar el despliegue. Las estadías se comparan como fechas YYYY-MM-DD con límite de salida exclusivo, que permite el recambio en la misma fecha. Los comprobantes por correo se intentan después de guardar para que un proveedor de email no bloquee el registro. Estas decisiones se constataron en código; no se encontraron ADR formales en el repositorio fuente, por lo que no se les atribuye una numeración inventada.

Seguridad y protección de datos

Las API privadas verifican JWT, sesión vigente y rol en el servidor; las mutaciones comprueban origen, tamaño y forma de la entrada. Las consultas de negocio usan parámetros y la exportación exige permiso. Admin y owner comparten actualmente el conjunto de datos de una sola organización; no hay aislamiento por cliente. Una auditoría local del 22 de septiembre identificó además riesgos pendientes en secretos históricos, dependencias, autenticación, reservas y recuperación. Estas defensas implementadas no equivalen a una certificación de seguridad ni prueban la configuración efectiva de producción.

Pruebas y alcance de validación

La validación de esta ficha usa un entorno aislado con el código fuente, Node.js 22.23.2, npm ci y datos sintéticos. Se ejecutaron la suite original Vitest sin cambiar aserciones, lint, TypeScript y build. El recorrido local cubrió acceso de una cuenta ficticia, creación y lectura de reserva, abono y persistencia; la navegación pública se inspeccionó aparte. Los resultados exactos, fallos y condiciones constan en la tabla y en el registro interno.

La cobertura v8 histórica del proyecto sólo incluye dos helpers declarados en vitest.config.mjs: no es cobertura del motor de reservas, controladores, interfaz ni producción. La antigua prueba de 20 solicitudes estaba construida sobre un simulador en memoria y no puede usarse como evidencia de contención real de SQLite.

Resultados y alcance de las comprobaciones
ComprobaciónResultadoAlcance
Suite original59/59 pruebas · 16 archivosVitest 4.1.11 en Node.js 22.23.2; ejecución en un entorno aislado, sin modificar aserciones.
Análisis y compilaciónLint, TypeScript y build aprobadosESLint, tsc --noEmit y build de Next.js 16.3.3 en la copia aislada.
Recorrido HTTPEscenarios principales aprobadosServidor compilado en loopback, base y cuenta sintéticas: acceso, creación, lectura, abono, rechazo de solapamiento y XLSX. SMTP deshabilitado.
Solicitudes concurrentes1 creación · 11 conflictos12 solicitudes HTTP lanzadas juntas a un único proceso y una base SQLite local. No prueba varios procesos ni carga sostenida.
Cambio de estadoDefecto reproducidoReactivar una reserva cancelada tras crear otra para fechas coincidentes dejó dos reservas activas solapadas.
Cobertura y producciónAlcance limitadoLa cobertura configurada mide sólo dos helpers. No se inspeccionaron servidor real, SMTP, restauración ni métricas de negocio.

Despliegue y operación

El repositorio prepara un build standalone de Next.js y contiene scripts para proceso Node, proxy y respaldos cifrados condicionados a claves de configuración. El sistema está en producción activa y lo utilizan dos personas administradoras. Esta revisión no entró al servidor ni verificó proxy, TLS, permisos, destino de archivos, jobs de respaldo, restauración o entrega de correo. La documentación de despliegue más antigua registra un incidente de pérdida de nombres e imágenes tras un deploy; el código posterior separa datos y release, pero su ejecución real requiere comprobación.

Resultado e impacto operativo

La herramienta ya permite a recepción registrar y consultar huéspedes, reservas, pagos y reportes desde un panel común. Su uso por dos administradoras y su puesta en marcha corresponden a la operación real del complejo. No se dispone de una medición trazable de sobreventas evitadas, tiempo de reserva, ahorro o adopción; por ello no se presentan porcentajes ni tiempos como resultado.

Evidencia y límites

Implementado: vitrina con contacto por WhatsApp, panel privado, controladores de reservas y pagos, SQLite WAL, exportación y notificación opcional. Comprobado localmente: los escenarios concretos de la tabla sobre una copia con datos sintéticos. Simulado: servicios externos y cualquier prueba de concurrencia basada en un motor en memoria. Operación en producción: desarrollo integral propio y uso continuo por dos administradoras. Pendiente: verificar estado del servidor, correo real, restauración, resultados de negocio y cierre de los defectos de integridad. El informe de auditoría del 22 de septiembre y el registro saneado de esta revisión se conservan internamente.

Evidencia de concurrencia y sobreventa

La muestra anterior de 20 tareas concurrentes sólo probaba una clase de memoria escrita para el portafolio. Esta revisión envió 12 solicitudes HTTP a la vez al controlador real y una base SQLite local: hubo una creación y once conflictos. Se usó un único proceso; no se midieron múltiples procesos ni carga sostenida. La ruta de cambio de estado permitió reactivar un solapamiento en el mismo entorno.

Observaciones operativas y despliegue real

Desarrollo integral propio, en producción activa con dos personas administradoras. No se usaron datos del servidor ni testimonios de las usuarias. Las cifras operativas siguen sin procedencia verificable.

Calidad de software y UX

La página pública muestra unidades y fotografías reales y facilita el contacto por WhatsApp; el panel reúne tablas y formularios para la recepción. La captura de cabecera corresponde a la vitrina pública, no a un motor de reserva en línea. La interfaz y este expediente se revisaron en escritorio y móvil, con teclado y desplazamiento del modal; ello describe la revisión local y no una certificación de accesibilidad del producto.

Muestra de código y prueba de ingeniería

Muestra pública mínima del predicado de solapamiento usado en la creación: compara dos intervalos de días de calendario de la misma cabaña y excluye reservas canceladas. No implementa transacciones, pagos ni la API.

JavaScript / Node.js
// Ejemplo aislado del criterio de solapamiento de reservas.
// Las fechas de entrada se validan en formato AAAA-MM-DD antes de esta comprobación.
export function overlaps(existing, candidate) {
return existing.cabinId === candidate.cabinId
&& existing.status !== 'Cancelada'
&& existing.checkIn < candidate.checkOut
&& existing.checkOut > candidate.checkIn;
}

Prueba de la muestra aislada

JavaScript / Node.js
import assert from 'node:assert/strict';
const stay = { cabinId: 1, status: 'Reservada', checkIn: '2026-04-10', checkOut: '2026-04-14' };
assert.equal(overlaps(stay, { cabinId: 1, checkIn: '2026-04-12', checkOut: '2026-04-15' }), true);
assert.equal(overlaps(stay, { cabinId: 1, checkIn: '2026-04-14', checkOut: '2026-04-17' }), false);
assert.equal(overlaps(stay, { cabinId: 2, checkIn: '2026-04-12', checkOut: '2026-04-15' }), false);
assert.equal(overlaps({ ...stay, status: 'Cancelada' }, { cabinId: 1, checkIn: '2026-04-12', checkOut: '2026-04-15' }), false);

La muestra y estas cuatro aserciones se ejecutaron exactamente como aparecen, como módulo Node.js aislado. El resultado demuestra el predicado para esos casos; la validación de fechas y la atomicidad se examinan por separado en el sistema real.