ENES
Volver

Bitácora Digital

Rescatar y modernizar una plataforma GovTech heredada

Una refactorización arquitectónica completa de una plataforma de supervisión de construcción en producción: de un monolito frágil a un SaaS multi-tenant con sincronización offline-first, trazabilidad inmutable y generación asíncrona de documentos.

FlutterLaravelFirebaseMySQLDigitalOcean SpacesClean Architecture

38,519

Reportes fotográficos en DB

2,948

Usuarios (Firebase Auth)

581

Minutas de obra

3,590

Proyectos

Bitácora Digital — project listBitácora Digital — inspectionsBitácora Digital — meeting minutes

El problema

`Un sistema que funcionaba, hasta que dejó de hacerlo`

Bitácora Digital es utilizada por supervisores de construcción, inspectores municipales y empresas privadas para documentar visitas de obra, listas de verificación y registros de cumplimiento en tiempo real, frecuentemente en lugares sin internet. La V1 llevaba años en producción con uso real acumulado, pero tres problemas estructurales se habían vuelto imposibles de ignorar.

Fallos de sincronización y pérdida de datos

El mecanismo de sincronización enviaba un payload JSON monolítico con fotos codificadas en Base64, frecuentemente superando los 20MB. En conexiones inestables, estos payloads fallaban a mitad de la transferencia. El cliente móvil no podía detectar éxito parcial, por lo que asumía que todo había pasado o reintentaba todo, generando registros duplicados y pérdida silenciosa de datos.

Colaboración multi-usuario rota

Para colaborar en un proyecto entre organizaciones, los usuarios necesitaban credenciales separadas para cada contexto. Los roles estaban hardcodeados como flags enteros. Agregar un supervisor de otra empresa a un proyecto requería workarounds en lugar de un modelo de permisos de primera clase.

Extensibilidad nula

Cada vez que un nuevo municipio o colegio profesional necesitaba incorporarse, el código requería modificación directa. No existía el concepto de organizaciones como entidades de primera clase, ni aislamiento de tenant, ni mecanismo para gestionar o comercializar plantillas de checklists entre clientes.

V1 vs V2

Antes V1

  • Recursos propios del usuario
  • Roles como columnas enteras (id_tipo)
  • Modelo de proyecto con un solo propietario
  • Sincronización monolítica (Base64 + JSON)
  • Checklists mutables (sin trazabilidad)
  • Generación de PDF síncrona
  • Auth social con contraseñas vacías
  • Frontend en AngularJS

Después V2

  • Datos propiedad de la organización (multi-tenant)
  • Roles Spatie + conjuntos de permisos por proyecto
  • Colaboración multi-stakeholder vía pivot
  • Sync asíncrono desacoplado (UUIDs + máquina de estados)
  • Versionado inmutable con linaje completo
  • PDF por cola con entrega por URL firmada
  • Firebase Admin SDK como proxy de identidad
  • Frontend en Next.js (Etapa 1 completa)

Decisiones de arquitectura

Cada decisión está documentada como un ADR en el repositorio público.

Multitenancy basado en organizaciones

Arquitectura

Se introdujo un modelo Organization como propietario raíz de todos los datos. Laravel Global Scopes aplican el aislamiento de tenant de forma transparente. Los usuarios pertenecen a múltiples organizaciones mediante un pivot, resolviendo el problema de credenciales múltiples sin migrar cuentas.

Versionado inmutable y marketplace

Integridad de datos

Los checklists son documentos legales. V2 nunca actualiza filas, cada cambio crea una versión nueva. Un root_uuid agrupa el linaje. valid_from/valid_until determinan la versión autoritativa en cualquier punto del tiempo. Las plantillas maestras pueden clonarse en la organización de un comprador como primitiva de marketplace.

Roles y permisos contextuales

Arquitectura

Los roles a nivel de organización son gestionados por Spatie e hidratados por middleware. Los permisos a nivel de proyecto viven en una tabla pivot como conjuntos de permisos desacoplados de la jerarquía organizacional. Los administradores pueden asignar overrides granulares por usuario sin cambios de esquema.

Protocolo de sync asíncrono con máquina de estados

Resiliencia

Se reemplazó el payload monolítico de 20MB por un rompecabezas de UUIDs: los binarios suben por separado, los metadatos sincronizan como referencias UUID (~95% más pequeño). Una máquina de tres estados (ORPHAN > PENDING_BINARY > AVAILABLE) maneja llegadas no secuenciales. SyncSessions con manifiestos y checksums permiten recuperación granular.

Late binding para race conditions

Resiliencia

Los jobs de Laravel Batch corren en paralelo, los registros hijo pueden llegar antes de que el padre esté persistido. Los jobs hijo se liberan de vuelta a la cola con backoff hasta que el padre exista. El circuit-breaking tras el máximo de reintentos registra un error Missing Dependency sin corromper el estado.

Generación de PDF asíncrona

Arquitectura

Los reportes complejos causaban timeouts en V1. V2 despacha GenerateReportJob a la cola en segundo plano. El frontend consulta el UUID del job por su estado y recibe una URL firmada temporal al completarse.

Firebase como proxy de identidad

Seguridad

V1 usaba contraseñas vacías para auth social. V2 usa Firebase Admin SDK para verificar idTokens del lado del servidor. Las cuentas sociales tienen contraseñas nulas, los intentos de login nativo quedan bloqueados. Soporte multi-dispositivo mediante tabla user_devices.

Integración de paywall: Stripe + RevenueCat

Monetización

Las suscripciones móviles son gestionadas por RevenueCat (App Store + Play Store); las suscripciones web por Stripe. La división por canal mantiene el cumplimiento de políticas de plataforma mientras conserva una única fuente de verdad para el estado de suscripción en el backend.

Sincronización de webhooks de RevenueCat

Resiliencia

Los eventos INITIAL_PURCHASE, RENEWAL y EXPIRATION despachan jobs asíncronos que actualizan el estado de suscripción de la organización. Cada webhook se persiste en rc_webhook_logs antes de procesarse. Un período de gracia de 3 días en expiración evita cortes abruptos por renovaciones con retraso.

Resultados

Trazabilidad de nivel forense

Los checklists inmutables con seguimiento de linaje completo impiden que los reportes históricos puedan alterarse retroactivamente, un requisito obligatorio para casos de uso de cumplimiento gubernamental.

Sincronización verdaderamente offline-first

Los fallos de sync ya no causan pérdida de datos ni duplicados. Los inspectores en obras con conectividad deficiente reciben confirmación granular de exactamente qué registros sincronizaron y cuáles necesitan reintento.

Multitenancy listo para SaaS

Incorporar un nuevo municipio o colegio profesional ya no requiere cambios en el código, es una operación de datos.

Estrategia de entrega en paralelo

V1 permaneció en producción durante todo el refactor. V2 se desarrolló en un entorno de staging separado, manteniendo a 2,948 usuarios activos sin interrupciones.

`Lo más difícil no fue la tecnología, sino tomar decisiones con información incompleta mientras mantenía el sistema funcionando. Cada ADR fue una conversación forzada con mi yo futuro sobre tradeoffs que no podría deshacer.`

Etapa 1 completa: backend, app móvil y frontend Next.js refactorizados y en staging. Migración a producción en curso. El roadmap V3 incluye migración a almacenamiento privado con URLs firmadas (ADR 0008).