feat: basic onboarding flow\

This commit is contained in:
Jose Selesan
2026-09-15 15:41:45 -03:00
parent 874b6e0cff
commit a92e9e77c3
37 changed files with 1775 additions and 42 deletions

View File

@@ -1,7 +1,9 @@
export * from './lib/problem-details.js';
export * from './lib/result.js';
export * from './schemas/enums.js';
export * from './schemas/groups.js';
export * from './schemas/health-check.js';
export * from './schemas/onboarding.js';
export * from './schemas/pagination.js';
export * from './schemas/payments.js';
export * from './schemas/problem-details.js';

View File

@@ -0,0 +1,21 @@
import { z } from 'zod';
// Días de la semana en que se dictan las clases.
export const weekdaySchema = z.enum([
'MONDAY',
'TUESDAY',
'WEDNESDAY',
'THURSDAY',
'FRIDAY',
'SATURDAY',
'SUNDAY',
]);
export type WeekDay = z.output<typeof weekdaySchema>;
// Procesadores de pago soportados.
export const paymentProviderSchema = z.enum(['MERCADO_PAGO', 'STRIPE']);
export type PaymentProvider = z.output<typeof paymentProviderSchema>;
// Frecuencia de cobro de un grupo.
export const billingTypeSchema = z.enum(['MONTHLY', 'PER_CLASS']);
export type BillingType = z.output<typeof billingTypeSchema>;

View File

@@ -1,4 +1,5 @@
import { z } from 'zod';
import { billingTypeSchema, weekdaySchema } from './enums.js';
import { createPageSchema, createPageSizeSchema, createPaginationSchema } from './pagination.js';
const isoDateTimeSchema = z.string().datetime();
@@ -13,6 +14,13 @@ export const GroupDtoSchema = z.object({
createdById: z.string(),
createdAt: isoDateTimeSchema,
updatedAt: isoDateTimeSchema,
// Horario y cobro: quedan vacíos si el grupo aún no tiene configurado el plan.
days: z.array(weekdaySchema).nullable(),
time: z.string().nullable(),
capacity: z.number().int().min(1).nullable(),
price: z.number().min(0).nullable(),
billingType: billingTypeSchema.nullable(),
dueDay: z.number().int().min(1).max(28).nullable(),
});
export type GroupDto = z.output<typeof GroupDtoSchema>;

View File

@@ -0,0 +1,76 @@
import { z } from 'zod';
import { billingTypeSchema, paymentProviderSchema, weekdaySchema } from './enums.js';
import { GroupDtoSchema } from './groups.js';
export type { BillingType, PaymentProvider, WeekDay } from './enums.js';
export { billingTypeSchema, paymentProviderSchema, weekdaySchema } from './enums.js';
// Paso 2: vincula la cuenta de cobro del profesor.
export const ConnectPaymentSchema = z.strictObject({
provider: paymentProviderSchema,
// Para el MVP se conecta en modo simulación / sandbox.
sandbox: z.boolean().default(true),
accessToken: z.string().optional(),
});
export type ConnectPayment = z.output<typeof ConnectPaymentSchema>;
export const ConnectPaymentResultSchema = z.object({
provider: paymentProviderSchema,
sandbox: z.boolean(),
connected: z.boolean(),
});
export type ConnectPaymentResult = z.output<typeof ConnectPaymentResultSchema>;
// Paso 3: crea el primer grupo del profesor.
export const CreateFirstGroupSchema = z.strictObject({
name: z
.string()
.trim()
.min(3, 'El nombre debe tener al menos 3 caracteres')
.max(60, 'El nombre es demasiado largo (máximo 60 caracteres)'),
days: z.array(weekdaySchema).min(1, 'Elegí al menos un día de clase'),
time: z
.string()
.regex(/^([01]?\d|2[0-3]):[0-5]\d$/, 'Elegí un horario válido (ej: 09:00)'),
capacity: z
.number({ invalid_type_error: 'Ingresá un cupo válido' })
.int({ message: 'El cupo debe ser un número entero' })
.min(1, 'El cupo debe ser al menos 1'),
price: z
.number({ invalid_type_error: 'Ingresá un precio válido' })
.min(0, 'El precio no puede ser negativo'),
billingType: billingTypeSchema,
dueDay: z
.number({ invalid_type_error: 'Ingresá un día válido' })
.int({ message: 'El día debe ser un número entero' })
.min(1, 'El día de vencimiento debe estar entre 1 y 28')
.max(28, 'El día de vencimiento debe estar entre 1 y 28'),
});
export type CreateFirstGroup = z.output<typeof CreateFirstGroupSchema>;
export const OnboardingGroupDtoSchema = GroupDtoSchema.extend({
days: z.array(weekdaySchema).nullable(),
time: z.string().nullable(),
capacity: z.number().int().min(1).nullable(),
price: z.number().min(0).nullable(),
billingType: billingTypeSchema.nullable(),
dueDay: z.number().int().min(1).max(28).nullable(),
});
export type OnboardingGroupDto = z.output<typeof OnboardingGroupDtoSchema>;
export const CreateFirstGroupResultSchema = z.object({
group: OnboardingGroupDtoSchema,
onboardingCompleted: z.boolean(),
});
export type CreateFirstGroupResult = z.output<typeof CreateFirstGroupResultSchema>;
// Estado del onboarding para retomar el flujo si se interrumpió.
export const onboardingStepSchema = z.enum(['NOT_STARTED', 'PAYMENT_CONNECTED', 'COMPLETED']);
export type OnboardingStep = z.output<typeof onboardingStepSchema>;
export const OnboardingStatusDtoSchema = z.object({
step: onboardingStepSchema,
paymentConnected: z.boolean(),
completed: z.boolean(),
});
export type OnboardingStatusDto = z.output<typeof OnboardingStatusDtoSchema>;