services/api/src/modules/auth/phone-verification.controller.js
Overview
Este archivo define los handlers HTTP de verificación telefónica dentro del módulo auth. Expone dos operaciones: iniciar la verificación y confirmar el código recibido por SMS. Ambos handlers validan el cuerpo, llaman al servicio de dominio y devuelven respuestas JSON mínimas. El router del módulo los monta en POST /auth/phone/verify/start y POST /auth/phone/verify/check, siempre detrás de requireAuth .
La intención del archivo es mantener la capa HTTP delgada. La lógica real de negocio vive en phone-verification.service.js, mientras que la validación de entrada vive en auth.validation.js fileciteturn0file2turn0file9.
[!NOTE] Este controlador no habla con Twilio directamente. Solo adapta la petición HTTP al servicio de dominio, que a su vez usa
phone-verification.client.jsfileciteturn2file2turn0file1.
How it works
flowchart TB
Client[Authenticated client] --> StartRoute[POST phone verify start]
Client --> CheckRoute[POST phone verify check]
StartRoute --> StartHandler[startPhoneVerificationRequest]
StartHandler --> StartValidation[validatePhoneVerificationStartInput]
StartValidation --> StartService[startPhoneVerificationForUser]
StartService --> StartResponse[200 alreadyVerified or SMS message]
CheckRoute --> CheckHandler[checkPhoneVerificationRequest]
CheckHandler --> CheckValidation[validatePhoneVerificationCheckInput]
CheckValidation --> CheckService[checkPhoneVerificationForUser]
CheckService --> CheckResponse[200 message and phoneVerifiedAt]
El flujo es lineal en ambos endpoints. Primero se valida req.body, después se llama al servicio con req.auth.userId, y al final se responde con JSON. Si algo falla, el handler entrega el error a next(error) para que lo resuelva el middleware global.
El contrato HTTP de este módulo está documentado como parte de la verificación de teléfono. Los tests cubren exactamente POST /auth/phone/verify/start y POST /auth/phone/verify/check .
Key components
{
"title": "startPhoneVerificationRequest",
"content": "Handler p\u00fablico que valida `req.body`, llama a `startPhoneVerificationForUser(req.auth.userId, input.phone)` y responde con un JSON m\u00ednimo. Devuelve `alreadyVerified` y un mensaje fijo.",
"type": "function",
"filePath": "services/api/src/modules/auth/phone-verification.controller.js",
"badges": [
"public"
]
}
{
"title": "checkPhoneVerificationRequest",
"content": "Handler p\u00fablico que valida `req.body`, llama a `checkPhoneVerificationForUser(req.auth.userId, input)` y responde con el mensaje de confirmaci\u00f3n y `phoneVerifiedAt`.",
"type": "function",
"filePath": "services/api/src/modules/auth/phone-verification.controller.js",
"badges": [
"public"
]
}
Data & fields
Contexto de autenticación
[
{
"name": "req.auth.userId",
"type": "string",
"required": true,
"description": "Identificador del usuario autenticado. El router lo prepara con `requireAuth` antes de llegar al controlador."
}
]
[!IMPORTANT] Este controlador asume una sesión válida.
auth.routes.jscolocarequireAuthantes de ambos handlers, así quereq.auth.userIdya debe existir cuando entran aquí .
Body de startPhoneVerificationRequest
[
{
"name": "phone",
"type": "string",
"required": true,
"description": "Número de teléfono en formato E.164. El controlador lo recibe ya validado por `validatePhoneVerificationStartInput`."
}
]
Respuesta de startPhoneVerificationRequest
[
{
"name": "alreadyVerified",
"type": "boolean",
"required": true,
"description": "Indica si el usuario pidió verificar exactamente su propio número ya verificado."
},
{
"name": "message",
"type": "string",
"required": true,
"description": "Mensaje breve para la UI. El texto cambia solo en el caso ya verificado."
}
]
- Si el número coincide con el número ya verificado del propio usuario, la respuesta es:
alreadyVerified: truemessage: "Este numero ya esta verificado en tu cuenta."
- En cualquier otro caso, la respuesta es:
alreadyVerified: falsemessage: "Te enviamos un codigo de verificacion por SMS."
Este comportamiento viene del servicio, no del controlador. El controlador solo serializa el resultado fileciteturn0file2turn0file3.
Body de checkPhoneVerificationRequest
[
{
"name": "phone",
"type": "string",
"required": true,
"description": "Número de teléfono en formato E.164. El controlador lo recibe ya normalizado y validado."
},
{
"name": "code",
"type": "string",
"required": true,
"description": "Código recibido por SMS. No puede venir vacío."
}
]
Respuesta de checkPhoneVerificationRequest
[
{
"name": "message",
"type": "string",
"required": true,
"description": "Mensaje de éxito para la interfaz."
},
{
"name": "phoneVerifiedAt",
"type": "string",
"required": true,
"description": "Fecha de verificación devuelta por el servicio. En JSON llega serializada como fecha."
}
]
Behaviors
startPhoneVerificationRequest— validareq.body, llama al servicio conreq.auth.userIdy responde200con una forma mínima.startPhoneVerificationRequest— siempre delega los errores anext(error).checkPhoneVerificationRequest— validareq.body, llama al servicio conreq.auth.userIdy devuelvephoneVerifiedAt.checkPhoneVerificationRequest— siempre delega los errores anext(error).
El patrón es deliberadamente simple. La validación de formato está en auth.validation.js, y la lógica de persistencia está en phone-verification.service.js fileciteturn0file9turn0file3.
API
POST /auth/phone/verify/start
Este endpoint inicia la verificación SMS para un teléfono autenticado. El router lo protege con requireAuth, y el controlador solo devuelve una respuesta mínima. La forma de 200 cambia según alreadyVerified fileciteturn1file2turn0file2turn0file3.
{
"title": "Start phone verification",
"description": "Inicia la verificaci\u00f3n de tel\u00e9fono para el usuario autenticado. Si el n\u00famero ya es el propio n\u00famero verificado del usuario, devuelve `alreadyVerified: true`. En cualquier otro caso, dispara el flujo SMS y devuelve `alreadyVerified: false`.",
"method": "POST",
"baseUrl": "http://localhost:3000/api/auth",
"endpoint": "/phone/verify/start",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"phone\": \"+525512345678\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Solicitud procesada correctamente.",
"body": "{\n \"alreadyVerified\": false,\n \"message\": \"Te enviamos un codigo de verificacion por SMS.\"\n}"
},
"400": {
"description": "El tel\u00e9fono no cumple el formato esperado.",
"body": "{\n \"message\": \"El telefono debe usar formato internacional E.164\"\n}"
}
}
}
POST /auth/phone/verify/check
Este endpoint confirma el código recibido por SMS. El controlador valida el cuerpo y devuelve la fecha de verificación cuando el servicio aprueba el código. La respuesta 200 incluye el mensaje de éxito y phoneVerifiedAt fileciteturn0file2turn0file3.
{
"title": "Check phone verification",
"description": "Confirma el c\u00f3digo SMS para el usuario autenticado. Si el servicio aprueba la verificaci\u00f3n, el backend devuelve la fecha `phoneVerifiedAt`.",
"method": "POST",
"baseUrl": "http://localhost:3000/api/auth",
"endpoint": "/phone/verify/check",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "application/json",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"phone\": \"+525512345678\",\n \"code\": \"123456\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Tel\u00e9fono verificado correctamente.",
"body": "{\n \"message\": \"Telefono verificado correctamente.\",\n \"phoneVerifiedAt\": \"2026-02-01T00:00:00.000Z\"\n}"
},
"400": {
"description": "El tel\u00e9fono o el c\u00f3digo no son v\u00e1lidos, o el c\u00f3digo no fue aprobado por el proveedor.",
"body": "{\n \"message\": \"El codigo no es valido o ya expiro\"\n}"
}
}
}
Gotchas & edge cases
[!WARNING] Este controlador no expone metadatos de Twilio. No devuelve SID, canal ni respuesta cruda del proveedor. Solo expone
alreadyVerified,messageyphoneVerifiedAtfileciteturn0file2turn2file2.
[!WARNING]
alreadyVerifiedno es decorativo. Solo valetruecuando el usuario reingresa su propio número ya verificado. Para cualquier otro caso, el valor esfalsey el mensaje es el mismo fileciteturn0file3turn2file8.
[!WARNING]
req.auth.userIddebe existir antes de entrar al handler. Esa garantía la darequireAuthenauth.routes.js, no este archivo .
[!NOTE] La validación del teléfono y del código no vive aquí.
validatePhoneVerificationStartInputyvalidatePhoneVerificationCheckInputestán enauth.validation.js.
Related
Este archivo se integra con varias piezas del mismo módulo. auth.routes.js lo monta en las rutas HTTP protegidas. auth.validation.js valida los cuerpos de entrada. phone-verification.service.js ejecuta la lógica de dominio. phone-verification.client.js encapsula el SDK de Twilio. Y services/api/test/phone-verification.test.js verifica el contrato completo de POST /auth/phone/verify/start y POST /auth/phone/verify/check fileciteturn1file2turn0file9turn0file3turn2file2turn2file0.
[!IMPORTANT] El alcance de este archivo es HTTP. La persistencia, la normalización de teléfono y la resolución de conflictos viven más abajo, en el servicio de dominio.