Saltar al contenido principal

Pasos del Flujo

  1. Interacción del Cliente (Capa de Presentación): La interacción del usuario ocurre en un componente React (page.tsx o container/ opcional) dentro de una aplicación apps/*.
  2. Llamada a Server Action (Punto de Entrada API): El componente invoca una función Server Action importada (@core/actions). Esta función está marcada con 'use server'.
  3. Validación de Entrada (Action): La Server Action valida la entrada cruda recibida del cliente usando esquemas Yup compartidos (@core/validation). Si la validación falla, devuelve un error inmediatamente (típicamente un ActionResult con success: false y validationErrors).
  4. Instanciar e Iniciar Máquina (Action): Si la validación tiene éxito, la Action:
    • Resuelve cualquier dependencia necesaria, como la implementación correcta del Service usando su factory (@core/services/[domain]/serviceMap.ts).
    • Instancia (interpret) la máquina XState relevante (@core/machines), proporcionando la instancia del servicio resuelta y potencialmente otras configuraciones a través del context inicial.
    • Inicia el intérprete de la máquina.
    • Envía el evento inicial (ej., { type: 'CREATE', input: validatedInput }) a la máquina, pasando la entrada validada (ej., desde InferType de Yup).
    • Escucha/espera a que la máquina alcance un estado final.
  5. Orquestar Lógica (Machine): La máquina XState transiciona a través de sus estados basada en eventos y su contexto interno. Su rol principal es la orquestación.
  6. Preparar Entrada de Servicio e Invocar (Machine): Dentro de estados específicos (a menudo usando servicios invoke o acciones entry/exit), la máquina:
    • Construye el DTO de Entrada del Servicio apropiado (definido en services/[domain]/domain/[Domain]Types.ts) usando datos de su contexto (como el validatedInput recibido en el evento inicial).
    • Llama al método apropiado en la implementación del Servicio (pasado vía contexto) usando el DTO preparado (ej., context.userService.createUser(serviceInputDto)).
  7. Procesar Salida de Servicio y Actualizar Contexto (Machine): Cuando la llamada al servicio invocada se completa:
    • La máquina recibe el DTO de Salida del Servicio (definido en services/[domain]/domain/[Domain]Types.ts).
    • Basándose en la estructura de este DTO de salida, la máquina actualiza su contexto interno (ej., extrayendo la entidad User creada desde outputDto.user y almacenándola en context.user, o almacenando un mensaje de error de un DTO de salida fallido en context.error).
  8. Alcanzar Estado Final (Machine): La máquina eventualmente transiciona a un estado terminal (ej., success, failure), indicado por type: 'final'.
  9. Devolver Resultado Estructurado (Action → Client): La Server Action, habiendo esperado el estado final de la máquina:
    • Inspecciona el estado final y el contexto de la máquina.
    • Formatea el resultado en un objeto ActionResult estándar (ej., { success: true, data: finalMachineContext.user } o { success: false, error: finalMachineContext.error }).
    • Devuelve este ActionResult al componente cliente que realizó la llamada.
Roles Claros:
  • Client (apps/*): Renderiza UI (mappnext/ds-tw), captura entrada del usuario, llama a Actions, muestra resultados/errores desde ActionResult.
  • Action (@core/actions): Límite de la API. Valida entrada (Yup), resuelve dependencias (Services), interpreta e inicia la Machine, espera resultado, formatea ActionResult.
  • Machine (@core/machines): Orquesta el flujo (XState). Mapea entrada validada a DTOs de Servicio, invoca métodos del Servicio, procesa DTOs de salida del Servicio, gestiona estado/contexto interno.
  • Service (@core/services): Ejecuta tareas discretas. Define contrato (Interface + DTOs en domain/), implementa lógica (Classes en implementations/), interactúa con DB/APIs externas.

Ejemplo Abreviado: Flujo Crear Usuario (Action → Machine → Service)

Este ejemplo simplificado demuestra el flujo central: Action valida e inicia la máquina, la máquina prepara un DTO y llama al servicio, el servicio realiza el trabajo usando su contrato definido, y el resultado fluye de regreso. 1. Tipos Base (@core/types)
// packages/core/types/entities/User.ts
// Define la entidad User
export interface User {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
}
// packages/core/types/machines/user/UserCreationMachineTypes.ts
import { User } from '../../entities/User';
import { CreateUserInput } from '../../../validation/userSchema'; // Tipo de entrada desde la capa de validación
import { UserService } from '../../../services/user/domain/UserService'; // Interfaz del Servicio

// Contexto de la Máquina: Almacena entrada validada, resultado final (User), error e instancia del servicio.
export interface UserCreationMachineContext {
  validatedInput?: CreateUserInput; // Contiene datos *después* de la validación Yup
  user?: User; // Contiene la entidad User final *después* de la llamada exitosa al servicio y procesamiento
  error?: string;
  userService?: UserService; // La instancia del servicio resuelta
}

// Eventos de la Máquina
export type UserCreationMachineEvent =
  | { type: 'CREATE'; input: CreateUserInput } // El evento lleva la entrada validada
  | { type: 'RETRY' };
2. Esquema de Validación (@core/validation)
// packages/core/validation/userSchema.ts
import * as yup from 'yup';

export const userSchema = yup.object({
  name: yup.string().required("Name is required"), // El nombre es requerido
  email: yup.string().email("Invalid email format").required("Email is required"), // Formato de email inválido, El email es requerido
});

// Tipo derivado del esquema de validación
export type CreateUserInput = yup.InferType<typeof userSchema>;
3. Definición del Servicio (@core/services/user/domain)
// packages/core/services/user/domain/UserTypes.ts
import { User } from '../../../types/entities/User';

// --- DTOs de Métodos de Servicio ---
// Define las formas de datos específicas para los métodos de UserService

// DTO de entrada para el método createUser
export interface CreateUserMethodInput {
  name: string;
  email: string;
}

// DTO de salida para el método createUser
// Caso simple: devuelve la entidad User creada.
export interface CreateUserMethodOutput {
  user: User;
}
// packages/core/services/user/domain/UserService.ts
// --- Interfaz del Servicio (Contrato) ---
// Define *qué* operaciones proporciona el servicio, usando los DTOs para las formas de datos.

import { CreateUserMethodInput, CreateUserMethodOutput } from './UserTypes';

export interface UserService {
  createUser(data: CreateUserMethodInput): Promise<CreateUserMethodOutput>;
  // Define otros métodos de servicio aquí usando sus respectivos DTOs desde UserTypes.ts
}
4. Implementación del Servicio (@core/services/user/implementations)
// packages/core/services/user/implementations/DefaultUserService.ts
import { UserService } from '../domain/UserService';
// *** Importa los DTOs de Método específicos definidos por el contrato del servicio ***
import { CreateUserMethodInput, CreateUserMethodOutput } from '../domain/UserTypes';
import { User } from '../../../types/entities/User'; // Necesario para construir la entidad interna/salida

// Implementación Concreta de la Strategy usando el contrato DTO definido
export class DefaultUserService implements UserService {
  // *** La firma del método usa los DTOs del servicio ***
  async createUser(data: CreateUserMethodInput): Promise<CreateUserMethodOutput> {
    console.log('DefaultUserService: Received service input DTO:', data); // DTO de entrada del servicio recibido

    // Simula interacción con BD o lógica de negocio usando datos del DTO de entrada
    const newUserEntity: User = { // Construye la entidad interna
      id: crypto.randomUUID(),
      name: data.name, // Usa datos del DTO de entrada
      email: data.email, // Usa datos del DTO de entrada
      createdAt: new Date(),
    };

    // await db.users.insert(newUserEntity); // Llamada real a la base de datos

    console.log('DefaultUserService: User entity created:', newUserEntity); // Entidad User creada

    // *** Devuelve el DTO de Salida del Servicio definido ***
    // (Coincidiendo con la estructura en UserTypes.ts)
    return { user: newUserEntity };
  }
}
5. Resolución del Servicio (@core/services/user/serviceMap.ts & index.ts)
// packages/core/services/user/serviceMap.ts
import { UserService } from './domain/UserService';
import { DefaultUserService } from './implementations/DefaultUserService';

const serviceImplementations: Record<string, UserService> = {
  default: new DefaultUserService(),
};

// Función Factory para obtener la implementación de servicio apropiada
export function getUserService(type: string = 'default'): UserService {
  const serviceInstance = serviceImplementations[type.toLowerCase()];
  if (!serviceInstance) {
      console.warn(`UserService strategy type "${type}" not found, falling back to default.`); // Tipo de estrategia no encontrado, volviendo al default.
      return serviceImplementations['default']; // Asegura que el default exista
  }
  return serviceInstance;
}
// packages/core/services/user/index.ts
// Archivo Barrel exportando la función factory
export { getUserService } from './serviceMap';
6. Máquina XState (@core/machines)
// packages/core/machines/userCreationMachine.ts
import { createMachine, assign } from 'xstate';
import {
    UserCreationMachineContext,
    UserCreationMachineEvent
} from '../types/machines/user/UserCreationMachineTypes';
// *** Importa los DTOs de Servicio necesarios para la invocación y procesamiento del resultado ***
import { CreateUserMethodInput, CreateUserMethodOutput } from '../services/user/domain/UserTypes';
import { UserService } from '../services/user/domain/UserService'; // Para tipar el contexto

export const userCreationMachine = createMachine({
  id: 'userCreation',
  types: {} as { context: UserCreationMachineContext, events: UserCreationMachineEvent },
  initial: 'idle',
  // El Context contiene la entrada validada, resultado potencial (user), error y servicio
  context: {
    validatedInput: undefined,
    user: undefined,
    error: undefined,
    userService: undefined,
  },
  states: {
    idle: {
      on: {
        CREATE: {
          target: 'creating',
          // Guard asegura que la instancia del servicio esté disponible en el contexto
          guard: ({ context }) => !!context.userService,
          // Action asigna la entrada validada del evento al contexto
          actions: assign({
            validatedInput: ({ event }) => event.input,
            error: undefined, // Limpia errores previos
          }),
        },
      },
    },
    creating: {
      // Invoca la llamada al servicio
      invoke: {
        id: 'invokeCreateUserService',
        // La función src prepara el DTO y llama al método del servicio
        src: async ({ context }) => {
          // Type guards por seguridad
          if (!context.userService) throw new Error('UserService missing in context'); // Falta UserService en el contexto
          if (!context.validatedInput) throw new Error('Validated input missing in context'); // Falta entrada validada en el contexto

          // *** Prepara el DTO CreateUserMethodInput para la llamada al servicio ***
          // Mapea datos desde context.validatedInput de la máquina a la forma del DTO de servicio.
          const serviceInput: CreateUserMethodInput = {
            name: context.validatedInput.name,
            email: context.validatedInput.email,
          };
          console.log('Machine: Calling createUser service with DTO:', serviceInput); // Máquina: Llamando al servicio createUser con DTO:

          // *** La Máquina invoca el método del servicio usando el DTO preparado ***
          return await context.userService.createUser(serviceInput);
        },
        // onDone maneja el resultado exitoso (DTO de Salida) del servicio
        onDone: {
          target: 'success',
          actions: assign({
             // *** Procesa el DTO CreateUserMethodOutput ***
             // Extrae los datos relevantes (la entidad User) del DTO de salida del servicio
             // y asígnalos al campo context.user de la máquina.
            user: ({ event }) => {
                const serviceOutput = event.output as CreateUserMethodOutput;
                console.log('Machine: Received service output DTO:', serviceOutput); // Máquina: DTO de salida del servicio recibido:
                return serviceOutput.user; // Extrae la propiedad user basada en la estructura del DTO
            },
          }),
        },
        // onError maneja fallos durante la invocación del servicio
        onError: {
          target: 'failure',
          actions: assign({
            error: ({ event }) => (event.error as Error)?.message ?? 'User creation failed in service', // La creación de usuario falló en el servicio
          }),
        },
      },
    },
    success: {
      // Estado final de éxito
      type: 'final',
    },
    failure: {
      // Estado final de fallo (podría permitir reintentos)
      on: { RETRY: 'creating' }
    },
  },
});
7. Server Action (@core/actions)
// packages/core/actions/userActions.ts
'use server';
import { userSchema, CreateUserInput } from '../validation/userSchema';
import { userCreationMachine } from '../machines/userCreationMachine';
import { getUserService } from '../services/user'; // Importa factory
import { User } from '../types/entities/User'; // Importa entidad para el tipo de resultado final
import { interpret } from 'xstate';
import * as yup from 'yup';

// Estructura de Resultado de Acción Estandarizada para el cliente
export interface ActionResult<T = any> {
  success: boolean;
  data?: T; // Contiene los datos finales relevantes (ej., el User creado)
  error?: string;
  validationErrors?: { path: string; message: string }[];
}

// La Server Action orquesta validación, resolución de servicio y ejecución de máquina
export async function createUserAction(
  input: CreateUserInput // Action recibe entrada cruda (pero con suerte con la forma correcta) del cliente
): Promise<ActionResult<User>> { // Promete la entidad User final en caso de éxito
  try {
    // 1. Validar Entrada usando esquema Yup
    await userSchema.validate(input, { abortEarly: false });
    const validatedInput = input; // Usa la entrada post-validación
    console.log('Action: Input validation successful'); // Validación de entrada exitosa

    // 2. Resolver la Instancia de Servicio requerida usando la factory
    const userService = getUserService(); // Obtener la implementación por defecto
    console.log('Action: Resolved UserService instance'); // Instancia UserService resuelta

    // 3. Interpretar y ejecutar la máquina XState
    console.log('Action: Interpreting userCreationMachine'); // Interpretando userCreationMachine
    // Usa una Promise para esperar a que la máquina alcance un estado final
    return new Promise((resolve) => {
      const machineActor = interpret(
        // Proporciona contexto inicial a la máquina:
        userCreationMachine.withContext({
            userService: userService, // Inyecta el servicio resuelto
            validatedInput: undefined, // La máquina empieza limpia, obtendrá entrada vía evento
            user: undefined,
            error: undefined,
        })
      ).onTransition((state) => {
          // 4. Resolver la Promise cuando la máquina alcanza un estado final
          if (state.matches('success')) {
            console.log('Action: Machine finished successfully. Final context:', state.context); // Máquina finalizada con éxito. Contexto final:
            // Extrae los datos finales del User del contexto de la máquina
            resolve({ success: true, data: state.context.user });
            machineActor.stop(); // Detiene el intérprete
          }
          if (state.matches('failure')) {
            console.error('Action: Machine finished with failure:', state.context.error); // Máquina finalizada con fallo:
            resolve({ success: false, error: state.context.error });
            machineActor.stop(); // Detiene el intérprete
          }
        })
        .start(); // Inicia el intérprete de la máquina

      // 5. Envía el evento inicial con la entrada validada para iniciar el proceso
      console.log('Action: Sending CREATE event to machine with validated input:', validatedInput); // Enviando evento CREATE a la máquina con entrada validada:
      machineActor.send({ type: 'CREATE', input: validatedInput });
    });

  } catch (error) {
    // Manejar Errores de Validación específicamente
    if (error instanceof yup.ValidationError) {
      console.error('Action: Validation failed:', error.inner); // Validación fallida:
      return {
        success: false,
        validationErrors: error.inner.map((err) => ({
          path: err.path ?? 'unknown',
          message: err.message,
        })),
      };
    }
    // Manejar Otros Errores Inesperados (ej., fallo en resolución de servicio, problemas de configuración de máquina)
    console.error('Action: Unexpected error during setup:', error); // Error inesperado durante la configuración:
    return {
      success: false,
      error: error instanceof Error ? error.message : 'An unexpected server setup error occurred.', // Ocurrió un error inesperado de configuración del servidor.
    };
  }
}
8. Página UI (apps/[appName]/app/.../page.tsx)
// apps/radicacion/app/crear-usuario/page.tsx (Ejemplo - UI Simplificada)
'use client';
import React, { useState, useTransition, FormEvent } from 'react';
import { createUserAction, ActionResult } from '@core/actions/userActions';
import { CreateUserInput } from '@core/validation/userSchema'; // La página usa el tipo de validación para el formulario
import { User } from '@core/types/entities/User'; // La página espera la entidad User final en el resultado
// --- Importa componentes reales del Design System ---
import { Button } from 'mappnext/ds-tw/atoms/Button'; // Reemplaza con tu ruta real
import { InputField } from 'mappnext/ds-tw/molecules/InputField'; // Reemplaza con tu ruta real
// --- ---

export default function CreateUserPage() {
  // El estado del formulario coincide con la forma CreateUserInput (de la validación)
  const [formData, setFormData] = useState<CreateUserInput>({ name: '', email: '' });
  const [isPending, startTransition] = useTransition();
  // El estado del resultado espera el ActionResult que contiene el User final o errores
  const [result, setResult] = useState<ActionResult<User> | null>(null);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setFormData((prev) => ({ ...prev, [name]: value }));
    if (result) setResult(null); // Limpia resultado al cambiar el formulario
  };

  const handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    setResult(null); // Limpia resultado previo
    startTransition(async () => {
      // Llama a la server action con los datos del formulario
      const actionResult = await createUserAction(formData);
      setResult(actionResult); // Actualiza el estado con el resultado de la action
      if (actionResult.success) {
         setFormData({ name: '', email: '' }); // Resetea el formulario en caso de éxito
         console.log("UI: User created!", actionResult.data); // ¡Usuario creado!
         // Opcionalmente mostrar toast de éxito, redirigir, etc.
      } else {
         console.error("UI: Creation failed:", actionResult.error || actionResult.validationErrors); // Creación fallida:
         // Errores de validación/servidor se muestran vía el estado result abajo
      }
    });
  };

  // Ayudante para obtener error de validación para una ruta de campo específica
  const getValidationError = (path: string): string | undefined => {
    return result?.validationErrors?.find(err => err.path === path)?.message;
  };

  return (
    <div className="p-4 max-w-md mx-auto">
      <h1 className="text-2xl font-bold mb-4">Crear Usuario (Ejemplo Simple)</h1>
      <form onSubmit={handleSubmit} className="space-y-4">
        {/* Campos de entrada vinculados al estado formData */}
        <InputField
          name="name"
          label="Nombre" // Cambiado a Español
          value={formData.name}
          onChange={handleChange}
          error={getValidationError('name')} // Muestra error de validación si existe
          disabled={isPending}
          required
        />
        <InputField
          name="email"
          label="Email" // Se mantiene
          type="email"
          value={formData.email}
          onChange={handleChange}
          error={getValidationError('email')} // Muestra error de validación si existe
          disabled={isPending}
          required
        />

        {/* Muestra error general del servidor (de fallo de máquina/servicio) */}
        {result && !result.success && result.error && (
           <p className="text-sm text-red-600 mt-2">Error: {result.error}</p>
        )}

        {/* Muestra mensaje de éxito */}
        {result?.success && result.data && (
           <p className="text-sm text-green-600 mt-2">¡Usuario "{result.data.name}" creado exitosamente!</p>
        )}

        {/* Botón de envío muestra estado pendiente */}
        <Button type="submit" disabled={isPending} className="w-full mt-4">
          {isPending ? 'Creando...' : 'Crear Usuario'}
        </Button>
      </form>
    </div>
  );
}

Resumen de Rutas de Importación de Ejemplo

Importaciones Típicas mostrando estructura correcta y uso de DTO
// Importaciones Core usando alias
import { createUserAction, ActionResult } from '@core/actions/userActions';
import { userSchema, CreateUserInput } from '@core/validation/userSchema';
import { getUserService } from '@core/services/user'; // Factory/Locator del Servicio (usado en configuración de Action/Machine)
import { DefaultUserService } from '@core/services/user/implementations/DefaultUserService'; // Implementación/Strategy específica (menos común fuera de tests/map)
import { UserService } from '@core/services/user/domain/UserService'; // Interfaz/Abstracción del Servicio (para tipado)
import { CreateUserMethodInput, CreateUserMethodOutput } from '@core/services/user/domain/UserTypes'; // DTOs de Métodos de Servicio (usados en Impl de Machine/Service)
import { userCreationMachine } from '@core/machines/userCreationMachine'; // Definición de Máquina (usada en Action)
import { User } from '@core/types/entities/User'; // Ejemplo de Tipo de Entidad Base
import {
    UserCreationMachineContext,
    UserCreationMachineEvent
} from '@core/types/machines/user/UserCreationMachineTypes'; // Ejemplo de Tipos de Máquina
import { PaginationResult } from '@core/types/common/PaginationResult'; // Ejemplo de Tipo Común
import { useDebounce } from '@hooks/useDebounce'; // Hook (Ejemplo)
import { getApiEndpoint } from '@config/endpoints'; // Config (Ejemplo)

// Importaciones del Design System (Reemplazar con rutas reales)
import { Button } from 'mappnext/ds-tw/atoms/Button';
import { InputField } from 'mappnext/ds-tw/molecules/InputField';
import { Modal } from 'mappnext/ds-tw/organisms/Modal';