Arquitectura · Caso de estudio

Cómo se construyó
Amoxcalli.

Amoxcalli tiene dos lados. El servidor (backend) guarda la información y aplica las reglas. Las apps (frontend) son lo que docentes y padres ven y usan, en la web y en Android. Aquí se explica cada lado en palabras simples y, debajo, con su detalle técnico.

01 · Vista general

Una puerta de entrada y tres sistemas independientes.

Las apps nunca hablan directo con los datos: todo pasa por una sola puerta de entrada que reparte cada petición. Detrás hay tres sistemas, uno para cuentas, otro para pagos y otro para la escuela. Cada uno guarda su propia información, así un problema en uno no detiene a los demás.

Detalle técnico: un API Gateway con YARP enruta por prefijo a tres microservicios. Cada servicio es dueño de su base de datos; entre servicios no hay llaves foráneas, se referencian por identificador y cada transacción se queda en una sola base.

Diagrama general de Amoxcalli Tres apps (docentes, padres y Android) se conectan a una puerta de entrada (API Gateway YARP), que reparte las peticiones entre tres servicios: cuentas (Identity), planes y pagos (Platform) y escuela (Academic). Cada servicio tiene su propia base de datos SQL Server. APP DOCENTES APP PADRES APPS ANDROID WEB · ANGULAR WEB · ANGULAR CAPACITOR PUERTA DE ENTRADA · API GATEWAY YARP · ENVÍA CADA PETICIÓN AL SERVICIO CORRECTO CUENTAS PLANES Y PAGOS ESCUELA IDENTITY PLATFORM ACADEMIC INICIO DE SESIÓN · ROLES ESCUELAS · USUARIOS SUSCRIPCIONES · COBROS FACTURACIÓN DIARIA CICLOS · GRUPOS · ALUMNOS CALIFICACIONES · ASISTENCIA BASE DE DATOS BASE DE DATOS BASE DE DATOS CADA SERVICIO CON SU PROPIA BASE SQL SERVER HTTP HTTP
02 · Backend: el servidor

El servidor: donde viven los datos y las reglas.

El servidor es la parte que no se ve. Guarda la información, revisa quién puede hacer qué y calcula cosas como el promedio final. Está ordenado por capas para que cada regla tenga un solo lugar.

Detalle técnico: .NET 10 con Clean Architecture. Cada microservicio tiene cuatro proyectos (Api, Application, Domain e Infrastructure) y las dependencias apuntan hacia el dominio. Lo común vive en librerías compartidas.

Cómo se guardan los datos

Toda la lectura y escritura de datos pasa por un mismo camino. Si un cambio tiene varios pasos, se guardan todos o ninguno.

  • Repositorio genérico IRepository<T> más repositorios específicos; las interfaces viven en Domain
  • Unit of Work con ExecuteInTransactionAsync para operaciones de varios pasos
  • EF Core con una configuración (IEntityTypeConfiguration) por entidad
  • Enums guardados como texto y datos iniciales cargados al arrancar

Nada se borra de verdad

Al eliminar algo, se marca como eliminado en lugar de borrarlo. Así queda historial y se puede recuperar.

  • Entidad base (BaseEntity) con Id GUID, fechas de auditoría y banderas Active y Deleted
  • Un filtro global de EF oculta lo eliminado en todas las consultas
  • Tablas nombradas en snake_case

Errores con mensaje claro

Cuando algo sale mal, el servidor responde siempre con el mismo formato y un mensaje entendible, que la app muestra tal cual.

  • Una excepción de dominio por caso: no encontrado, conflicto, prohibido, pago requerido…
  • Un middleware las convierte en el código HTTP correcto con un cuerpo uniforme
  • Los controllers quedan limpios, sin bloques try/catch

Seguridad

Al iniciar sesión recibes un pase digital con fecha de vencimiento. Si alguien roba uno viejo y lo usa, el sistema lo detecta y cierra la sesión.

  • Un solo servicio (Identity) emite el pase (token JWT) y todos los demás lo validan
  • Refresh token rotativo: si se reutiliza uno, se invalida toda la familia
  • Contraseñas cifradas con PBKDF2-SHA256 y bloqueo tras varios intentos fallidos
  • Avisos de pago verificados con firma HMAC-SHA256 y llaves de idempotencia, para no cobrar dos veces

Varias escuelas, una cuenta

Un docente puede trabajar en varias escuelas y cambiar de una a otra sin cerrar sesión. Solo ve los datos de la escuela activa.

  • Multi-tenant: la escuela, el ciclo y el grupo activos viajan dentro del token (claims)
  • Cambiar de contexto llama a un endpoint que emite un token nuevo
  • Un guard de institución compartido protege los tres servicios

Tareas automáticas

Cada día, sin que nadie intervenga, el servidor revisa las suscripciones y hace los cobros que tocan.

  • BackgroundService de facturación diaria, con la hora de México
  • Un scope de inyección de dependencias por ejecución
  • Los pasos secundarios, como enviar un correo, pueden fallar sin deshacer lo ya guardado
03 · Frontend: las apps

Las apps: lo que docentes y padres usan.

Son dos aplicaciones: una para docentes y escuelas, y otra para madres, padres y tutores. Las dos están hechas con la misma base y las mismas reglas, así que una mejora en la estructura sirve para ambas. Funcionan en el navegador y como app de Android.

Detalle técnico: dos proyectos Angular 21 hermanos, con la misma arquitectura y la misma configuración de calidad.

  • App docente: 15 módulos (ciclos, grupos, alumnos, asistencia, calificaciones, reportes, suscripción…), conectada a los tres servicios
  • App padres: 9 módulos (hijos, calificaciones, asistencia, tareas, historial médico…), conectada a cuentas y escuela
  • Cerca de 500 pruebas automáticas entre las dos (Vitest)
  • Publicadas en la web (Cloudflare) y como APK de Android (Capacitor 7)
ÁTOMO MOLÉCULA ORGANISMO PLANTILLA
04 · Frontend en detalle

Qué pasa cuando tocas un botón.

Cada clic recorre el mismo camino. La pantalla avisa lo que quieres hacer, un intermediario se encarga de pedirlo al servidor y, cuando llega la respuesta, la pantalla se actualiza sola. Como siempre es el mismo camino, cuando algo falla es fácil saber dónde buscar.

Recorrido de un clic en las apps La pantalla envía la intención a una fachada; la fachada la registra en el store de NgRx; un efecto llama al servidor con un cliente generado desde OpenAPI; la petición llega a la puerta de entrada del backend; la respuesta vuelve al store y la pantalla se actualiza mediante signals. PANTALLA FACHADA ESTADO CLIENTE API SERVIDOR LO QUE VES COMPONENTE RECIBE TU INTENCIÓN FACADE + SIGNALS MEMORIA DE LA APP NGRX STORE + EFFECTS HABLA CON EL SERVIDOR OPENAPI + INTERCEPTOR PUERTA DE ENTRADA API GATEWAY LA RESPUESTA VUELVE AL ESTADO Y LA PANTALLA SE ACTUALIZA SOLA TU CLIC

Base moderna y rápida

La app solo redibuja la parte de la pantalla que cambió, no la pantalla completa. Por eso responde rápido incluso en teléfonos sencillos.

  • Angular 21 sin zone.js (zoneless): la pantalla se actualiza con signals
  • Todos los componentes son standalone, sin NgModules
  • Detección de cambios OnPush y nueva sintaxis @if / @for

Orden que se revisa solo

El código está dividido por temas (alumnos, asistencia, pagos…). Una herramienta revisa automáticamente que nadie rompa esa división.

  • Carpetas core/, shared/ y features/; cada feature expone solo su archivo index.ts
  • ESLint prohíbe rutas ../../ y entrar a otro feature sin pasar por su index.ts
  • Atomic Design: el prefijo de cada componente indica su nivel (atm-, mol-, org-, temp-)
  • shared/ no puede depender de features ni del store

Pantallas y permisos

Cada sección se descarga solo cuando la abres, y antes de mostrarla la app revisa que tengas permiso para verla.

  • Todas las rutas se cargan bajo demanda (lazy loading), incluido el marco principal de la app
  • 6 guards en la app docente: sesión, invitado, rol, configuración inicial, suscripción y administrador; 2 en la de padres
  • Si sales de calificaciones con cambios pendientes, se guardan antes de salir (canDeactivate)

La memoria de la app

La app recuerda lo que ya cargó, como tus grupos o tus alumnos, para no pedirlo de nuevo. Las pantallas nunca tocan esa memoria directamente: siempre pasan por un intermediario.

  • NgRx con 18 slices de estado en la app docente y 7 en la de padres
  • Slices uniformes con createActionGroup, createFeature y effects; cada uno se registra con su ruta
  • Fachadas que exponen los datos como signals (toSignal); una regla de ESLint impide importar el store en un componente
  • Al abrir la app se restaura la sesión guardada

Conexión con el servidor

El código que habla con el servidor se genera automáticamente. Si el servidor cambia algo, la app avisa antes de publicarse en lugar de fallarle al usuario.

  • ng-openapi-gen crea funciones y modelos a partir del Swagger de cada servicio; un script los regenera todos
  • Un interceptor añade el token a cada petición
  • Si la sesión vence (401), la renueva una sola vez y pone en espera las demás peticiones
  • Mensajes automáticos de éxito y de error: sin conexión, tiempo agotado o aviso del servidor

Interfaz y formularios

Los botones, campos y ventanas vienen de una librería probada, adaptada a los colores de Amoxcalli y traducida por completo al español.

  • PrimeNG 21 con un preset propio sobre Aura y design tokens de color y tipografía en SCSS
  • Formularios reactivos con validaciones y un asistente de configuración de 7 pasos
  • Lectura de QR con la cámara (ZXing), que se descarga solo al usarla; la app de padres genera el QR de cada alumno
  • Confirmación antes de acciones delicadas con un ConfirmService basado en Promise
05 · Decisiones de diseño

Primero las reglas, después las pantallas.

Antes de diseñar pantallas se escribió un documento con reglas comunes: cómo se crea, se edita o se borra algo. Así cada pantalla nueva se comporta igual que las demás y el usuario no tiene que aprender cada una.

  • Pensado para usarse también en el teléfono: el menú lateral se vuelve un cajón y las ventanas ocupan toda la pantalla
  • Crear y editar se hace en una ventana (modal), sin perder de vista la lista
  • Cada vista muestra cuando está cargando; en la app de padres un mismo componente cubre carga, vacío y error
  • Antes de borrar algo, la app pide confirmación
  • Un asistente inicial de 7 pasos deja todo listo, con valores sugeridos
  • Cada app tiene su color principal sobre la misma base de diseño
06 · Móvil, calidad y publicación

Del código al teléfono.

Antes de llegar al usuario, el código pasa por pruebas automáticas y revisiones. Con el mismo código se publica la versión web y la app de Android.

App de Android

La misma app web se empaqueta como app de Android, sin escribir otra desde cero.

  • Capacitor 7 con los plugins de app, barra de estado y pantalla de inicio
  • Peticiones por la capa nativa (CapacitorHttp), sin problemas de CORS
  • Un servicio detecta si corre como app y ajusta funciones; por ejemplo, oculta los pagos dentro del APK
  • Un comando compila la web, sincroniza y genera el APK

Pruebas automáticas

Cerca de 500 pruebas revisan en segundos que lo que ya funcionaba siga funcionando después de cada cambio.

  • Vitest, con cada prueba junto al archivo que revisa (~450 en la app docente y ~63 en la de padres)
  • Los effects se prueban contra los clientes generados del API
  • Fachadas probadas con provideMockStore, y guards con sus propias pruebas
  • ESLint, Prettier, EditorConfig y límites de tamaño del bundle

Publicación y documentación

Cada cambio queda registrado con un formato estándar y la web se publica con un solo comando.

  • Mensajes de commit validados en GitHub Actions con un estándar de ramas y mensajes
  • Web publicada en Cloudflare (Workers con Wrangler)
  • Documentación viva: guía de arquitectura y contrato de endpoints
  • Regla: si un documento no coincide con el código, manda el código
Stack completo

Tecnologías usadas.

Servidor (backend)
  • .NET 10
  • C# 13
  • ASP.NET CORE
  • EF CORE 10
  • YARP
  • JWT
  • SWAGGER / OPENAPI
  • BREVO
Apps (frontend)
  • ANGULAR 21
  • SIGNALS
  • NGRX 21
  • RXJS
  • TYPESCRIPT 5.9
  • PRIMENG 21
  • SCSS
  • NG-OPENAPI-GEN
  • ZXING
Datos y pagos
  • SQL SERVER
  • MERCADO PAGO
  • HMAC-SHA256
Móvil, calidad y publicación
  • CAPACITOR 7
  • VITEST
  • ESLINT
  • PRETTIER
  • GITHUB ACTIONS
  • CLOUDFLARE
Pruébalo

Mira el resultado en vivo.

Todo lo que se describe aquí funciona detrás de las dos demos.